Linux之(6)Kconfig基础知识

Author:onceday date:2022年8月25日

本文档收集整理于互联网,可参考以下文档:

  • Kconfig Language linux kernel
  • Kconfig详解 大海中的一粒沙 博客园
  • menuconfig语法 IoT小胡 CSDN博客
  • Linux系统移植:menuconfig 原理分析 Top嵌入式 CSDN博客
  • Linux驱动篇(四)——Kconfig Volador 知乎
  • Linux内核配置Kconfig语法 诗小法 CSDN博客

1.引言

Kconfig是用于内核编译的,其是makefile的帮手,用于定制编译的组件。

在c源码的编译过程中,makefile指定c编译的环境变量,头文件,库文件,编译参数等。

但是并不是每一个C源码文件都是必须的。

因此,Kconfig就会指定当前文件夹内的内核(组件)配置情况。

在整个工程内,每个源码目录内都用makefile和kconfig,许多层次的makefile+Kconfig组成了分布式数据库,控制了整个大工程的编译情况。

Kconfig ---> (每个源码目录下)提供选项
.config ---> (源码顶层目录下)保存选择结果
Makefile---> (每个源码目录下)根据.config中的内容来告知编译系统如何编译

.config的内容就是根据Kconfig配置情况生成的 ,在现在,可以通过命令行的GUI界面来进行配置,非常方便,其文件也命名为Mconfig(menuconfig)。

一般执行以下命令即可出现图形化界面:

make menuconfig

从Kconfig中读出配置菜单,用户配置完后保存到.config(在顶层目录下生成)中。

在内核编译时,主Makefile调用这个.config,就知道了用户对内核的配置情况。

Kconfig的文件名很多,vscode市场也有相关扩展,搜索Kconfig,第一个即是。

2.Kconfig概念

Kconfig文件的名字千奇百怪,如config.in,Kconfig等,所以不要简单的从外表来区分他们。

需要依赖于同目录下的conf程序来执行。这其实就是Linux内核进行Kconfig操作的主程序之一了,类似的还有mconf,qconfgconf等。他们其实都是host program。关于它们是如何被编译出来的,还请参见 scripts/kconfig/Makefile 文件,主要是借助于bisonflexgperf三个工具来生成c源程序文件,之后再编译出来的,具体过程这里暂时不讨论。

2.1 mainmenu 主菜单

该菜单是输入make menuconfig以后打开的默认界面 :

mainmenu "项目工程名字"

这个"项目工程名字"将作为菜单一进入的标题。整个配置目录是一个树结构:

这里是内核工程标题(mainmenu)
+- 内核架构(menu)
|  +- 大端(config)
+- 组件(menu)
|  +- Networking support
|  +- System V IPC
|  +- BSD Process Accounting
|  +- Sysctl support
+- 模块(menu)
|  +- 核心(menu)
|     +- Set version information on all module symbols
|     +- Kernel module loader
+- ...

每一个实际配置条目(config,choice等)都具有自己的依赖关系,如果依赖关系不满足,将不会出现在菜单配置可选项里面。

如果一个配置条目的上级配置菜单不可见,那么该配置条目也不可见。

2.2 source 包含其他目录的Kconfig

使用这个关键字,就可以将其他目录的Kconfig包含进来,注意这里并没有目录层次关系,可以在目录里面包含外部的Kconfig文件。

source "xxx/Kconfig" //这里可以为绝对路径或者相对路径

顶层Kconfig文件包含的其他Kconfig将生成子菜单项,并拥有自己的菜单界面。

#主菜单
mainmenu "Onceday Kconfig test"
#引用其他文件
source "arch/Kconfig"
2.3 menu/endmenu 菜单条目

menu生成菜单的条目,而endmenu代表该条目的结尾。

注意,实际上由于菜单条目的嵌套规则,因此具体层次需要仔细分析。

如(注意中文的支持有限,小心乱码):

menu "选项1"
config MY_CHOICE_1bool "chose me"default y
endmenu

3.config 菜单配置项

注意config、choice等具体配置项和menu菜单条目的区别和相同。

两者都会在菜单配置界面生成条目,但menu相当于文件系统的目录,是一个容器,会以一个独立的界面容纳具体的配置项。config和choice相当于文件,是具体的可供选择的配置项。

config MODVERSIONSbool "Set version information on all module symbols"depends on MODULEShelpUsually, modules have to be recompiled whenever you switch to a newkernel.  ...

config里面可以包含类型、依赖、帮助信息、关联、输入提示、默认值等多条信息,后面将一一介绍。

MODVERSIONS是关键字,用于全局标识,在生成的.config文件里面,作为标识符来用。

config选项的关键字可以重复命名,但是config必须有一个不重复的输入提示 ,并且相互之间输入类型不能冲突。

3.1 输入类型(bool、tristate、string、hex、int)

每个config项必须有一个类型定义,基础类型只有两个,tristate和string。

  • tristate ,三态,表示选择的种类:

    1. y – 将驱动编译进内核镜像
    2. n - 不编译
    3. m - 将驱动编译为ko形式

以下两种定义方式是等效的:

bool "Networking support"
bool
prompt "Networking support"
3.2 输入提示(prompt)
"prompt" <tips> ["if" <expr>]

每个配置实体最多拥有一个输入提示,仅可选if进行依赖判断。

3.3 默认值(default)
"default" <expr> ["if" <expr>]

一个配置条目可以有多个默认值,但仅只有第一个默认值会生效。

默认值并没有被限制在config配置所在位置,可以在其他的位置修改,并且被一个更早定义的值覆盖。

如果用户没有设置值,才会使用默认值,默认值比prompt的优先级更高,所以会优先显示默认值

默认值一般都被预设为n ,这样会避免未知的效果,这么做有好处,那就是尽量减少工程需要编译的模块。

以下是关于默认值y/m的推荐配置:

  • 新增的Kconfig选项,如果它总是被编译,那么应该默认为default y
  • 一个不包含实际编译代码的选项,用来开启下层选项,那么应该被设为default y
  • 驱动程序的子驱动或一些类似配置选项默认为default n。可以改成default y以确保拥有正常的默认值。
  • 常用的硬件或者底层结构,如CONFIG_NETCONFIG_BLOCK,这种情况不多见。
3.4 类型定义+默认值(type definition + default value)
"def_bool"/"def_tristate" <expr> ["if" <expr>]

这是缩写形式,和上文分开写的效果是等同的。

3.5 依赖 (depends on)
"depends on" <expr>

定义了菜单项的依赖,如果需要依赖多个选项,它们可以用&&连接。

在一个config实体里面,depends会依赖到该实体内的所有选项上。

bool "foo" if BAR
default y if BAR
# 下面等效
depends on BAR
bool "foo"
default y
3.6 反向依赖(reverse dependencies) (select)
"select" <symbol> ["if" <expr>]

只能选择tristateboolean两种类型。(类型为n,m,y)

正常的depend on依赖,降低了符号(symbol)对应的上限(the upper limit),即该符号最大上限取决于其依赖的对象。

B depend on A, 如果 A = m(编译ko), 那么B只能可选n/m

反向依赖(reverse dependencies)则是限定另一个符号的下限(the lower limit),如下所示:

B select A, 如果 B = m,则默认 A = m, 且只能选择m和y。

如果一个配置符号(symbol)被选择(select)了多次,那么其限制值为所有select中最大的一个。

B select A,C select A, 如果 B = n,C = m,则默认 A = m, 且只能选择m和y。

select最好只用于没有依赖,且不可见的配置符号(config symbol)

B select A,A depend on C

对于上面这一种情况,如果C=n,则B设置为y时,A也会被强制设为yselect过程根本不会去检查C的情况。(实际menuconfig测试发现会检查,所以这个可能依赖于实现)。

因此,需要小心使用select这个属性。

3.7 弱反向依赖(weak reverse dependencies)(imply)

该选项的作用和select是类似的,也会强制去设置选中符号的最小值。

B imply A,A depend on C

但是对于上面这种情况,B 设置为y时,会去考虑A的依赖C的值,确保A的值符合其自身依赖的情况。

(实际在menuconfig测试发现,如果A有依赖depends on,则B无法影响A的值,所以具体情况还是要看实现。)

config FOOtristate "foo"imply BAZconfig BAZtristate "baz"depends on BAR

下面是可能的情况:

FOO BAR BAZ’s default choice for BAZ
n y n N/m/y
m y m M/y/n
y y y Y/m/n
n m n N/m
m m m M/n
y m m M/n
y n n N
3.8 限制菜单显示(Visible)
"visible if" <expr>

这个属性尽可以应用于菜单块,如果条件是假的,那么菜单块不会对用户显示,不过此配置符号仍然可以被其他符号选择select

这个属性和条件prompt语句很像,默认值为visible

3.9 整数范围(range)
"range" <symbol> <symbol> ["if" <expr>]

对于整数和十六进制数,这个可以限制可能的输入范围。用户仅可以输入一个值,其大于等于第一个symbol值且小于等于第二个symbol值。

3.10 帮助文本(help)
help this is first help information.this is second help info.this is third....<other symbol>

如上,帮助文本可以填写多行,其结束取决缩进等级。

只要后面某行其缩进空格数比help的第一行文本小,那么就是其结尾行。

3.11 模块属性(modules)

其声明一个符号被用于MODULES symbol,这个对于所有的配置符号,会启用第三种模块化状态。

4. 菜单项依赖

依赖关系定义了菜单项的可见性,还可以减少三态符号的输入范围。表达式中使用的三态逻辑比正常的布尔逻辑多使用一个状态来表示模块状态。依赖表达式的语法如下:

<expr> ::= <symbol>                           (1)<symbol> '=' <symbol>                (2)<symbol> '!=' <symbol>               (3)<symbol1> '<' <symbol2>              (4)<symbol1> '>' <symbol2>              (4)<symbol1> '<=' <symbol2>             (4)<symbol1> '>=' <symbol2>             (4)'(' <expr> ')'                       (5)'!' <expr>                           (6)<expr> '&&' <expr>                   (7)<expr> '||' <expr>                   (8)

表达式按优先级递减顺序列出(下面序号对应上面的不同表达式的序号):

  1. 将各种符号的值转换成一个表达式值,布尔值(Boolean)和三态值(tristate)转化成各自对应的值。所有其他的符号类型都转化为N
  2. 如果两个符号的值相等,那么返回y,否则返回n
  3. 如果两个符号的值相等,那么返回n,否则返回y
  4. 如果两个符号的值满足比较关系,那么返回y,否则返回n
  5. 返回表达式的值,用于覆盖的优先级。
  6. 返回结果(2 - /expr/)
  7. 返回结果min(/expr/, /expr/)
  8. 返回结果max(/expr/, /expr/)

一个表达式的值可以是nmy,或者0,1,2,菜单项的值只在my时是可见的。

有两种类型的符号:常数符号和非常数符号。非常数符号是最常见的符号,用’ config '语句定义。非常数符号完全由字母数字字符或下划线组成。常量符号只是表达式的一部分。常量符号总是用单引号或双引号括起来。在引号内,允许任何其他字符,并且可以使用``对引号进行转义。

5. 菜单结构

在菜单树中的实体位置有两种定义方式,一种是显示指定的。

menu "Network device support"depends on NETconfig NETDEVICES...endmenu

所有的菜单实体在menuendmenu之间,正如上面所示,NETDEVICES就是Network device support的一个子菜单选项。

所有子实体都集成了菜单实体的依赖关系,即NET依赖也在选项NETDEVICES的依赖列表中。

生成菜单结构的另一种方法是分析依赖项。如果一个菜单条目以某种方式依赖于前一个条目,那么它可以成为前一个条目的子菜单。首先,前面的(父)符号必须是(子符号)依赖项列表的一部分,然后下面两个条件之一必须为真:

  • 如果父条目设置为n,则子条目必须变为不可见的。

  • 子条目必须是可见的,如果父条目是可见的:

config MODULESbool "Enable loadable module support"config MODVERSIONSbool "Set version information on all module symbols"depends on MODULEScomment "module support disabled"depends on !MODULES

MODVERSIONS直接依赖于MODULES,这意味着它只有在MODULES不同于n时才可见。另一方面,注释(comment)只有在MODULES被设置为n时才可见。

6. Kconfig 语句

配置文件描述了一系列菜单项,其中每一行都以一个关键字开始(帮助文本除外)。以下关键字结束菜单项:

  • config,配置符号,或者一个菜单实体。
  • menuconfig,可选的菜单
  • choice/endchoice,单选框, choice 中的 config 参数只能bool 或 tristate
  • comment,一行注释
  • menu/endmenu,进入下个界面的菜单, 点击Enter键就能够进入这个menu所对应的界面
  • if/endif, 逻辑判断
  • source,导入其他Kconfig文件
6.1 config 定义配置符号
config <symbol><config options>
6.2 menuconfig 可选菜单
"menuconfig" <symbol><config options>

这类似于上面的简单配置条目,但它也给前端提供了一个提示,即所有子选项都应该作为一个单独的选项列表显示。为了确保所有子选项都真正地显示在menuconfig条目下,而不是在它的外面,从<config options>列表必须依赖于menuconfig符号。在实践中,这可以通过使用下面两个结构之一来实现:

(1):
menuconfig M
if Mconfig C1config C2
endif(2):
menuconfig M
config C1depends on M
config C2depends on M

在下面的例子(3)(4)中,C1C2仍然有M依赖关系,但不会再出现在menuconfig M下,因为C0,它不依赖于M:

(3):
menuconfig Mconfig C0
if Mconfig C1config C2
endif(4):
menuconfig M
config C0
config C1depends on M
config C2depends on M
6.3 choices 选择框
"choice" [symbol]
<choice options>
<choice block>
"endchoice"

这将定义一个选择组,并接受上述任何属性作为选项。选项只能是bool类型或三态类型。如果没有为一个选项指定类型,那么它的类型将由组中第一个选择元素的类型决定,或者如果所有选择元素都没有指定类型,则保持未知。

布尔选择只允许选择单个配置项,而三态选择还允许将任意数量的配置项设置为’ m '。如果一个硬件存在多个驱动程序,并且只有一个驱动程序可以编译/加载到内核中,但所有驱动程序都可以编译为模块,则可以使用这种方法。

一个选项接受另一个选项optional,它允许将选项设置为n,并且不需要选择任何条目。如果没有symbol与一个选择相关联,那么您就不能有该选择的多个定义。如果一个符号与该选项相关联,那么您可以在另一个地方定义相同的选项(即具有相同的条目)。

6.4 comment 注释
"comment" <prompt>
<comment options>

这将定义一个注释,该注释在配置过程中显示给用户,并且还将返回到输出文件中。唯一可能的选择是依赖关系。

6.5 menu 菜单
"menu" <prompt>
<menu options>
<menu block>
"endmenu"

这定义了一个菜单块,参见上面的“菜单结构”。唯一可能的选项是依赖项和“可见”属性。

6.6 if 判断
"if" <expr>
<if block>
"endif"

它定义了一个if块。依赖性表达:附加到所有附加的菜单项。

6.7 source 导入源文件
"source" <prompt>

它读取指定的配置文件。这个文件总是被解析。

6.8 mainmenu
"mainmenu" <prompt>

如果配置程序选择使用它,这将设置配置程序的标题栏。它应该放在配置的顶部,在任何其他语句之前。

6.9 #Kconfig源文件注释

#会把所在一行剩余的部分当成注释。

7. Kconfig Macro Language 宏支持

详情可参考:Kconfig Macro Language linux kernel

基本的想法是由Make启发的。当我们看Make时,我们注意到两种语言在一起。一种语言描述由目标和先决条件组成的依赖图。另一种是用于执行文本替换的宏语言。

SRC := foo.c
CC := gcc$(APP): $(SRC)$(CC) -o $(APP) $(SRC)

不用宏的版本如下:

foo: foo.cgcc -o foo foo.c

借助Make的思想,Kconfig也采用宏语言:

CC := gccconfig CC_HAS_FOOdef_bool $(shell, $(srctree)/scripts/gcc-check-foo.sh $(CC))

Kconfig中的宏语言将源文件处理为以下中间文件:

config CC_HAS_FOOdef_bool y

最后再使用标准Kconfig语言来解决这个问题。

7.1 宏变量

Kconfig中的宏语言将源文件处理为中间文件:与Make一样,Kconfig中的一个变量作为宏变量工作。宏变量被“就地(in place)”展开以生成文本字符串,然后该文本字符串可以进一步展开。

要取得变量的值,请将变量名包含在$()中。即使是单字母变量名,括号也是必须的;

$X是语法错误。也不支持${CC}中的花括号形式。

变量有两种类型:

  • 简单展开的变量(Simply expanded variable),简单展开的变量是使用:=赋值操作符定义的。从Kconfig文件读取该行后,其右侧立即展开。

  • 递归展开的变量(recursively expanded variable),递归展开的变量使用=赋值操作符定义。它的右边只是存储为变量的值,而没有以任何方式展开它。相反,展开是在使用变量时进行的。

还有另一种类型的赋值操作符+=用于向变量添加文本。如果+=的左边最初定义为一个简单变量,那么它的右边会立即展开。否则,它的求值将被延迟

变量引用可以接受如下形式的形参:

$(name,arg1,arg2,arg3)

可以将参数化引用视为函数。(更准确地说,是“用户定义函数”,而不是下面列出的“内置函数”)。

有用的函数在使用时必须展开,因为如果传递不同的参数,相同的函数将展开不同。因此,使用=赋值操作符来定义“用户自定义函数”。参数在主体定义中通过$(1)$(2)等引用。

实际上,递归扩展变量和用户定义函数在内部是相同的。(换句话说,“变量”是“零参数的函数”。)广义上的“变量”包括“用户定义函数”。

7.2 内建函数

与Make一样,Kconfig提供了几个内置函数。每个函数都有特定数量的参数。

在Make中,每个内置函数都至少接受一个实参。Kconfig允许内置函数零参数,例如$(filename)$(lineno)。你可以把它们看作是“内置变量”,但这只是我们如何称呼它的问题。我们在这里说“内置函数”是指本机支持的功能。

当前Kconfig支持的内建函数如下:

  • $(shell, command),“shell”函数接受一个参数,该参数被展开并传递给子shell执行。然后读取命令的标准输出,并将其作为函数的值返回。输出中的每一个换行符都替换为一个空格。删除尾随的换行符。不返回标准错误,也不返回任何程序退出状态。
  • $(info, text)," info "函数接受单个参数并将其打印到标准输出。它的计算结果是一个空字符串。
  • $(warning-if, condition, text)warning-if函数接受两个参数。如果条件部分是y ,文本部分被发送到stderr。文本的前缀是当前Kconfig文件的名称和当前行号。
  • $(error-if, condition, text)error-if函数类似于warning-if,但如果条件部分为y ,则它立即终止解析。
  • $(filename)filename不带参数,$(filename)被展开为被解析的文件名。
  • $(lineno)lineno不带参数,$(lineno)被展开为被解析的行号。
7.3 Make vs Kconfig 区别

kconfig采用了Make-likemacro语言,但是函数调用语法略有不同。

一个在Make里面的函数调用如下:

$(func-name arg1,arg2,arg3)

函数名和第一个参数之间至少用一个空格隔开。然后,从第一个参数中删除前导空白,而保留其他参数中的空白。您需要使用一种技巧,将第一个参数以空格开头。例如,如果你想让" info "函数打印 " hello",你可以这样写:

empty :=
space := $(empty) $(empty)
$(info $(space)$(space)hello)

Kconfig只使用逗号作为分隔符,并在函数调用中保留所有空格。有些人喜欢在逗号分隔符后面加一个空格:

$(func-name, arg1, arg2, arg3)

在这种情况下,funcname将接收" arg1"" arg2"" arg3"。前导空格的存在可能取决于函数。这同样适用于Make,例如,$(subst .c, .o, $(sources))是一个典型的错误;它将.c”替换为“ .o”

在Make中,使用内置函数’ call '引用用户定义函数,如下所示:

$(call my-func,arg1,arg2,arg3)

Kconfig以相同的方式调用用户定义函数和内置函数。省略call使语法更短。

在Make中,有些函数逐字处理逗号而不是参数分隔符。例如,$(shell echo hello, world)执行命令echo hello, world。同样,$(info hello, world)“hello, world”打印到stdout。你可以说这是一种有用的不一致。

在Kconfig中,为了更简单的实现和语法一致性,出现在$()上下文中的逗号总是分隔符。它的意思是:

$(shell, echo hello, world)

是一个错误,因为它传递了两个参数,而shell函数只接受一个参数。要在参数中传递逗号,可以使用以下技巧:

comma := ,
$(shell, echo hello$(comma) world)
7.4 注意事项

变量(或函数)不能跨标记展开。因此,不能使用变量作为由多个标记组成的表达式的简写。

以下形式有效:

RANGE_MIN := 1
RANGE_MAX := 3config FOOint "foo"range $(RANGE_MIN) $(RANGE_MAX)

但以下形式是错误的

RANGES := 1 3config FOOint "foo"range $(RANGES)

变量不能在Kconfig中展开为任何关键字。以下操作无效:

MY_TYPE := tristateconfig FOO$(MY_TYPE) "foo"default y

显然,从设计来看,$(shell命令)在文本替换阶段进行了扩展。你不能把符号传递给’ shell '函数。

以下形式是无效的:

config ENDIAN_FLAGstringdefault "-mbig-endian" if CPU_BIG_ENDIANdefault "-mlittle-endian" if CPU_LITTLE_ENDIANconfig CC_HAS_ENDIAN_FLAGdef_bool $(shell $(srctree)/scripts/gcc-check-flag ENDIAN_FLAG)

但是可以按以下形式来写:

config CC_HAS_ENDIAN_FLAGbooldefault $(shell $(srctree)/scripts/gcc-check-flag -mbig-endian) if CPU_BIG_ENDIANdefault $(shell $(srctree)/scripts/gcc-check-flag -mlittle-endian) if CPU_LITTLE_ENDIAN

Linux之(6)Kconfig基础知识相关推荐

  1. Linux系统编程——进程基础知识

    Linux系统编程--进程基础知识 1.程序和进程 程序,是指编译好的二进制文件,在磁盘上,不占用系统资源(cpu.内存.打开的文件.设备.锁-) 进程,是一个抽象的概念,与操作系统原理联系紧密.进程 ...

  2. r语言工作路径linux,R语言实用基础知识_工作路径-注释-安装和卸载R包_2019-12-01...

    R语言的实用基础知识有很多,都是我在工作和学习中所整理的,有的是看书整理的,也有的是从网络上的各种博客.各种资源获取的,所以我采用日更的方式进行支持整理和更新,希望能够帮到屏幕前的你! 今天是我日更的 ...

  3. Linux——Linux C语言编程基础知识

    源程序的编译 在Linux下面,如果要编译一个C语言源程序,我们要使用GNU的gcc编译器. 通常在gcc后跟一些选项和文件名来使用gcc编译器.gcc 命令的基本用法如下:: gcc [option ...

  4. liteos内核驱动和linux,移植RTOS必备基础知识

    1. 基础知识 移植内核对技术的要求比较高.比较细. 1.1 单片机相关的知识 栈的作用 加载地址.链接地址 重定位 几个简单的硬件知识 ○串口 ○定时器 中断的概念 1.2 Linux操作相关的知识 ...

  5. linux vim tag,Vim基础知识之ctags 及 Taglist 插件

    Vim基础知识之ctags 及 Taglist 插件 1. 我的界面 2. ctags exuberant ctags是一般Linux系统上缺省的ctags 我的ctags版本:Exuberant C ...

  6. Linux运维是什么?linux运维的基础知识

    如果您对运维行业了解一些,应该会知道,现在的运维早已不是早年的"睡机房",往办公室打眼一看,分不清是运维攻城狮还是开发程序猿,但是,运维这行也是春天到了,今天Linux,明天云计算 ...

  7. Linux应用程序开发 基础知识

     Linux应用程序开发 本文讲述了linux应用程序开发的基本内容.值得学习! Copyright © 2006 本文遵从GNU 的自由文档许可证(Free Documentation Lice ...

  8. 2020-06-19 云运维linux centos7.2 文件管理基础知识总结

    2020-06-19 云运维linux文件管理基础知识总结 linux=Linux: 命令(基础使用)+文件系统(目录)+服务(配置) 1.路径的分类 绝对路径:由根目录(/)开始写起的文件名或目录名 ...

  9. Linux的基本操作与基础知识

    Linux基本操作与基础知识 Linux和Winodws的区别 Linux系统的目录结构 常见目录说明 文件类型 文件权限 基本命令 vi vim使用 文件查看命令 查找文件方法 grep 强大的文本 ...

最新文章

  1. 替换ExpandableListView右边箭头Group Indicator(小图标)
  2. 微软创立全新人工智能实验室,与DeepMind、OpenAI同台竞技
  3. cas实现单点登录原理
  4. matlab模块里有s,求助!!S-Function做通用模块
  5. Cannot find module ‘https-proxy-agent‘
  6. 人工智能翻译能否替代人工翻译,人工智能翻译何去何从
  7. Mac中Safari浏览器转换IE各版本
  8. 项目管理十大知识领域和五大过程
  9. 新浪博客的html,新浪博客代码-HTML代码
  10. CNC加工中心的刀具补偿详解
  11. u盘安装系统win2019服务器系统,U盘启动装WIN10系统教程,U盘安装WIN2019方法,UltraISO将Windows server 2016/2019安装盘ISO写入U盘进行安装...
  12. 智能家居之远程视频监控
  13. Android各版本源码网盘下载(不断更新)
  14. python header函数_Header函数
  15. Excel快速下拉填充序列至10000行
  16. 如何使用云桌面进行开发?
  17. M1 芯片打开软件提示:“XXXX” 因为出现问题而无法打开 怎么解决?
  18. ##配置 SEP+RRPP 混合环组网
  19. 微信小程序云开发-微信小程序账号申请及新手环境配置
  20. NEO4J的安装配置及使用总结

热门文章

  1. Lambda表达式(3)|(List对象转map)
  2. SQL Server创建表语句介绍
  3. kali安装教程kali换源往kali拖拽文件
  4. Win7 瘦身 winsxs文件夹
  5. Ubuntu22下载安装
  6. 关于latex报错GPL Ghostscript 9.50的解决办法
  7. java7u45下载_jdk-7u45-windowi586 32位 求官网
  8. JS控制网页中Flash影片的播放(附带各参数)
  9. java guice_如何在Guice中进行需要注入实例的动态绑定?
  10. cJSON库用法详解