Author:onceday date:2022年8月25日
本文档收集整理于互联网,可参考以下文档:
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,第一个即是。
Kconfig文件的名字千奇百怪,如config.in,Kconfig等,所以不要简单的从外表来区分他们。
需要依赖于同目录下的conf程序来执行。这其实就是Linux内核进行Kconfig操作的主程序之一了,类似的还有mconf
,qconf
和gconf
等。他们其实都是host program。关于它们是如何被编译出来的,还请参见 scripts/kconfig/Makefile
文件,主要是借助于bison
,flex
和gperf
三个工具来生成c源程序文件,之后再编译出来的,具体过程这里暂时不讨论。
该菜单是输入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等)都具有自己的依赖关系,如果依赖关系不满足,将不会出现在菜单配置可选项里面。
如果一个配置条目的上级配置菜单不可见,那么该配置条目也不可见。
使用这个关键字,就可以将其他目录的Kconfig包含进来,注意这里并没有目录层次关系,可以在目录里面包含外部的Kconfig文件。
source "xxx/Kconfig" //这里可以为绝对路径或者相对路径
顶层Kconfig文件包含的其他Kconfig将生成子菜单项,并拥有自己的菜单界面。
#主菜单
mainmenu "Onceday Kconfig test"
#引用其他文件
source "arch/Kconfig"
menu生成菜单的条目,而endmenu代表该条目的结尾。
注意,实际上由于菜单条目的嵌套规则,因此具体层次需要仔细分析。
如(注意中文的支持有限,小心乱码):
menu "选项1"
config MY_CHOICE_1bool "chose me"default y
endmenu
注意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必须有一个不重复的输入提示 ,并且相互之间输入类型不能冲突。
每个config项必须有一个类型定义,基础类型只有两个,tristate和string。
y
– 将驱动编译进内核镜像n
- 不编译m
- 将驱动编译为ko形式以下两种定义方式是等效的:
bool "Networking support"
bool
prompt "Networking support"
"prompt" ["if" ]
每个配置实体最多拥有一个输入提示,仅可选if
进行依赖判断。
"default" ["if" ]
一个配置条目可以有多个默认值,但仅只有第一个默认值会生效。
默认值并没有被限制在config配置所在位置,可以在其他的位置修改,并且被一个更早定义的值覆盖。
如果用户没有设置值,才会使用默认值,默认值比prompt
的优先级更高,所以会优先显示默认值。
默认值一般都被预设为n
,这样会避免未知的效果,这么做有好处,那就是尽量减少工程需要编译的模块。
以下是关于默认值y/m
的推荐配置:
default y
。default y
。default n
。可以改成default y
以确保拥有正常的默认值。CONFIG_NET
,CONFIG_BLOCK
,这种情况不多见。"def_bool"/"def_tristate" ["if" ]
这是缩写形式,和上文分开写的效果是等同的。
"depends on"
定义了菜单项的依赖,如果需要依赖多个选项,它们可以用&&
连接。
在一个config实体里面,depends会依赖到该实体内的所有选项上。
bool "foo" if BAR
default y if BAR
# 下面等效
depends on BAR
bool "foo"
default y
"select" ["if" ]
只能选择tristate
和boolean
两种类型。(类型为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
也会被强制设为y
,select
过程根本不会去检查C
的情况。(实际menuconfig测试发现会检查,所以这个可能依赖于实现)。
因此,需要小心使用select
这个属性。
该选项的作用和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 |
"visible if"
这个属性尽可以应用于菜单块,如果条件是假的,那么菜单块不会对用户显示,不过此配置符号仍然可以被其他符号选择select
。
这个属性和条件prompt
语句很像,默认值为visible
。
"range" ["if" ]
对于整数和十六进制数,这个可以限制可能的输入范围。用户仅可以输入一个值,其大于等于第一个symbol
值且小于等于第二个symbol
值。
help this is first help information.this is second help info.this is third....
如上,帮助文本可以填写多行,其结束取决缩进等级。
只要后面某行其缩进空格数比help的第一行文本小,那么就是其结尾行。
其声明一个符号被用于MODULES
symbol,这个对于所有的配置符号,会启用第三种模块化状态。
依赖关系定义了菜单项的可见性,还可以减少三态符号的输入范围。表达式中使用的三态逻辑比正常的布尔逻辑多使用一个状态来表示模块状态。依赖表达式的语法如下:
::= (1) '=' (2) '!=' (3) '<' (4) '>' (4) '<=' (4) '>=' (4)'(' ')' (5)'!' (6) '&&' (7) '||' (8)
表达式按优先级递减顺序列出(下面序号对应上面的不同表达式的序号):
N
。y
,否则返回n
。n
,否则返回y
。y
,否则返回n
。(2 - /expr/)
。min(/expr/, /expr/)
。max(/expr/, /expr/)
。一个表达式的值可以是n
,m
,y
,或者0,1,2
,菜单项的值只在m
,y
时是可见的。
有两种类型的符号:常数符号和非常数符号。非常数符号是最常见的符号,用’ config '语句定义。非常数符号完全由字母数字字符或下划线组成。常量符号只是表达式的一部分。常量符号总是用单引号或双引号括起来。在引号内,允许任何其他字符,并且可以使用``对引号进行转义。
在菜单树中的实体位置有两种定义方式,一种是显示指定的。
menu "Network device support"depends on NETconfig NETDEVICES...endmenu
所有的菜单实体在menu
和endmenu
之间,正如上面所示,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
时才可见。
配置文件描述了一系列菜单项,其中每一行都以一个关键字开始(帮助文本除外)。以下关键字结束菜单项:
Enter
键就能够进入这个menu所对应的界面config
"menuconfig"
这类似于上面的简单配置条目,但它也给前端提供了一个提示,即所有子选项都应该作为一个单独的选项列表显示。为了确保所有子选项都真正地显示在menuconfig
条目下,而不是在它的外面,从
列表必须依赖于menuconfig
符号。在实践中,这可以通过使用下面两个结构之一来实现:
(1):
menuconfig M
if Mconfig C1config C2
endif(2):
menuconfig M
config C1depends on M
config C2depends on M
在下面的例子(3)
和(4)
中,C1
和C2
仍然有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
"choice" [symbol]
"endchoice"
这将定义一个选择组,并接受上述任何属性作为选项。选项只能是bool类型或三态类型。如果没有为一个选项指定类型,那么它的类型将由组中第一个选择元素的类型决定,或者如果所有选择元素都没有指定类型,则保持未知。
布尔选择只允许选择单个配置项,而三态选择还允许将任意数量的配置项设置为’ m '。如果一个硬件存在多个驱动程序,并且只有一个驱动程序可以编译/加载到内核中,但所有驱动程序都可以编译为模块,则可以使用这种方法。
一个选项接受另一个选项optional
,它允许将选项设置为n
,并且不需要选择任何条目。如果没有symbol
与一个选择相关联,那么您就不能有该选择的多个定义。如果一个符号
与该选项相关联,那么您可以在另一个地方定义相同的选项(即具有相同的条目)。
"comment"
这将定义一个注释,该注释在配置过程中显示给用户,并且还将返回到输出文件中。唯一可能的选择是依赖关系。
"menu"
这定义了一个菜单块,参见上面的“菜单结构”。唯一可能的选项是依赖项和“可见”属性。
"if"
"endif"
它定义了一个if块。依赖性表达:附加到所有附加的菜单项。
"source"
它读取指定的配置文件。这个文件总是被解析。
"mainmenu"
如果配置程序选择使用它,这将设置配置程序的标题栏。它应该放在配置的顶部,在任何其他语句之前。
#
Kconfig源文件注释#
会把所在一行剩余的部分当成注释。
详情可参考: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语言来解决这个问题。
Kconfig中的宏语言将源文件处理为中间文件:与Make一样,Kconfig中的一个变量作为宏变量工作。宏变量被“就地(in place)”展开以生成文本字符串,然后该文本字符串可以进一步展开。
要取得变量的值,请将变量名包含在$()
中。即使是单字母变量名,括号也是必须的;
$X
是语法错误。也不支持${CC}
中的花括号形式。
变量有两种类型:
简单展开的变量(Simply expanded variable),简单展开的变量是使用:=
赋值操作符定义的。从Kconfig文件读取该行后,其右侧立即展开。
递归展开的变量(recursively expanded variable),递归展开的变量使用=
赋值操作符定义。它的右边只是存储为变量的值,而没有以任何方式展开它。相反,展开是在使用变量时进行的。
还有另一种类型的赋值操作符+=
用于向变量添加文本。如果+=
的左边最初定义为一个简单变量,那么它的右边会立即展开。否则,它的求值将被延迟。
变量引用可以接受如下形式的形参:
$(name,arg1,arg2,arg3)
可以将参数化引用视为函数。(更准确地说,是“用户定义函数”,而不是下面列出的“内置函数”)。
有用的函数在使用时必须展开,因为如果传递不同的参数,相同的函数将展开不同。因此,使用=
赋值操作符来定义“用户自定义函数”。参数在主体定义中通过$(1)
、$(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)
被展开为被解析的行号。kconfig采用了Make-like
macro语言,但是函数调用语法略有不同。
一个在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)
变量(或函数)不能跨标记展开。因此,不能使用变量作为由多个标记组成的表达式的简写。
以下形式有效:
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