0x01 前言
在构建linux内核之前,我们先要为linux内核生成一份.config配置文件。
该配置文件的作用,就是声明哪些代码会被编译进内核,哪些代码会被编译成模块,以及为内核编译期所需的静态参数,提供一种配置途径。
生成.config配置文件的方式有很多种,具体我们可以看内核的 make help 命令输出:
看上图中的 Configuration targets 和 Configuration topic targets 部分,有非常多的make命令,可以用来生成.config配置文件。
那这些生成.config配置文件的方式之间,有什么区别呢?
我们在构建linux内核时,又如何选择呢?
下面我们来一一讲下。
0x02 make defconfig
对于新手来说,这个命令是最简单的,用来生成.config配置文件的方式。
它会根据不同的平台,使用内核代码里不同的预定义配置文件,生成一份默认配置。
比如,对于x86_64平台来说,它使用的内核代码里的预定义配置文件,就是 arch/x86/configs/x86_64_defconfig。
有关不同的平台,具体使用的是哪个预定义配置文件,这个是在平台相关的 Makefile 里,通过设置 KBUILD_DEFCONFIG 变量指定的。
如果想了解 make defconfig 命令的详细执行逻辑,可以看下其对应的 Makefile 源码,以及该命令最终执行的 conf 程序的源码。
下面我们来演示下这个命令:
由上图中的输出可见,.config配置文件,就是根据 x86_64_defconfig 这个预定义配置文件生成的。
这里有一点需要注意,x86_64_defconfig预定义配置文件里,并没有定义所有的内核配置项的值,它只定义了部分。
对于那些未定义的内核配置项,如果它在Kconfig中声明了默认值,那写到.config文件里的,就是这个默认值。
如果它没有声明默认值,那写到.config文件里的,就是该内核配置项所属类型的零值。
即,如果某个内核配置项的类型为int,那它的零值就是0,如果类型为string,那它的零值就是空,以此类推。
0x03 make nconfig / menuconfig / xconfig / gconfig
这四个命令都是以图形化且可交互的方式,来生成.config文件。
只不过 make nconfig 和 make menuconfig 是在终端里显示图形化界面,而 make xconfig 和 make gconfig 是独立的图形化应用程序。
make nconfig可以看成是一个新版的make menuconfig,所以如果你喜欢在终端里工作,推荐使用make nconfig。
make xconfig 和 make gconfig 的区别是,前者使用Qt实现,后者使用GTK+实现,所以如果你喜欢图形化程序,可以在这两个中挑选。
下面演示下 make nconfig:
在上图的程序里,你就可以通过各种快捷键,对内核配置进行修改了。
在修改完之后,退出程序,完整的内核配置就会保存到.config文件里。
其他三个命令的操作也都类似,这里就不一一展示了。
这四个命令其实只是界面显示以及交互方式不同,它们的核心逻辑都是一样的。
在启动时,它们都是先尝试加载当前已有的.config配置,如果没找到,再尝试在各个指定路径加载默认配置。
并且如果使用了某个默认配置,这个默认配置文件路径,会在终端里输出出来。
比如,这是我刚刚执行的make nconfig命令的输出:
看上图中的选中部分,它显示 make nconfig 使用的默认配置是 arch/x86/configs/x86_64_defconfig,这个和之前的 make defconfig 命令使用的默认配置是一样的。
在加载完当前配置或默认配置后,这四个命令就会显示一个图形化界面,用户就可以在这个界面里为内核进行配置了,后续逻辑就基本一样了。
0x04 make config
这个命令其实和 make nconfig / menuconfig 类似,都是在终端里为内核进行配置。
只不过 make nconfig / menuconfig 使用的是图形化界面,而 make config 使用的是命令行。
下面我们来演示下:
由上图可以看到,make config 是通过命令行的方式,对内核的所有配置项,一项一项的进行配置。
这个对比 make nconfig 的图形化方式,就显得非常麻烦了,所以不推荐使用。
不过除了这个交互形式不同之外,其他逻辑都是类似的。
比如,make config也会先尝试加载当前.config配置,如果没找到,则再尝试加载默认配置。
在加载完配置文件之后,再显示上述界面,让用户修改配置。
在修改完之后,最终再把这些配置保存到.config文件里。
0x05 make oldconfig
一般在生成了一份.config配置文件之后,我们是会把它保存下来的,这样在后续编译内核的时候,就可以一直使用这个配置文件,而不用再对内核进行重复配置。
但有时候,内核里定义的配置项是有可能被修改的,比如最常见的,就是在我们更新内核代码后,内核里会增加很多新的配置项,而这些配置项,是在我们原有的.config配置文件里没有配置的。
此时我们可以删掉原有的.config配置文件,然后再重新配置一份。
但通常情况下,我们没必要这么做,我们完全可以基于当前的.config配置,只对新增的配置项进行额外配置。
针对这种情况,我们就可以使用 make oldconfig 命令。
下面我们来演示下。
我们先用 make defconfig 命令,生成一份默认的.config配置。
然后再修改内核里的Kconfig文件,新增一个配置项T1:
接着执行 make oldconfig 命令:
看上图中,该命令只要我们对新增的T1进行配置,其他配置项根本不用配置,直接使用了原值。
我们可以用git命令,看下执行完 make oldconfig 命令后,.config文件的变化:
.config文件中只是新增了T1配置项,其他配置项并没有发生变化。
这里有个小知识,就是每次make命令要把新的内核配置写到.config文件之前,都会把原.config文件重命名为.config.old,这也是为什么上面新增了一个.config.old文件的原因。
有了.config.old这个文件,我们就可以通过各种方式来了解.config文件的前后变化了,比如通过上图中的git命令。
0x06 make olddefconfig
该命令和 make oldconfig 一样,也是以当前.config配置文件为基础,只对新增配置项进行配置。
只不过它不会向用户询问新增项的值,而是直接使用它们的默认值或者零值。
还是使用之前类似的例子再演示下。
先用 make defconfig 生成一份.config配置文件,然后再在Kconfig中添加一些配置项:
接着执行 make olddefconfig 命令:
看上图,这次就没有再让我们输入新增配置项的值了,而是直接使用了默认值或者零值。
看下.config前后的变化:
.config中新增了T1和T2配置项,且T1的值为其声明的默认值t1,T2的值为其类型的零值,即空字符串。
0x07 make localmodconfig
每个linux发行版都有自己的内核配置,我们可以使用 zcat /proc/config.gz 命令,查看当前正在运行的内核,在编译时使用的是什么配置:
有时候,我们想基于这个配置,来编译一个新的内核。
这时,我们就可以使用 zcat /proc/config.gz > .config 命令,生成一份.config配置文件,然后就可以开始编译了。
但如果你这么操作,你就会发现编译内核的时间非常长,比使用内核默认配置的时间长很多。
这是因为,各linux发行版为了兼容各种硬件,会把很多在默认情况下禁用的内核代码,都编译成模块,这样当遇到相关硬件时,对应的模块就会被动态加载进内核,如此这个硬件才可以被使用。
这样做对linux发行版来说是有必要的,但对于我们自己为自己编译内核来说,是完全没有必要的。
因为我们只是使用固定的几个硬件,完全没有必要把那么多不会用到的内核代码编译成模块。
那我们既想使用当前运行内核的配置,又不想编译那么多不会用到的内核模块,这个怎么做呢?
此时,我们就可以使用 make localmodconfig 命令。
该命令会检查我们当前正在使用的.config配置,如果没有的话,就用当前正在运行的内核使用的.config配置,检查它里面启用了哪些模块,也就是值为m的那些配置项。
如果配置文件里启用了某模块,但该模块并没有实际加载到我们当前正在运行的内核里,也就是说,当前运行内核并没有使用该模块,则该模块对应的配置项会被禁用。
当所有模块都检查完毕后,最终的配置会再写到.config文件里。
这样,我们最后得到的.config配置,就是一份和当前运行内核使用的配置类似,但剔除掉我们不用的模块的配置了。
我们使用这份配置,就可以构建出一个和当前运行linux类似的内核,但又不必花费过长的编译时间。
0x08 make localyesconfig
这个命令和 make localmodconfig 类似,只不过它除了有 make localmodconfig 的所有功能外,还会把最终.config里所有值为m的配置项,改成值为y。
也就是说,这些配置项对应的代码不会再编译成模块,而是直接被编译到内核里。
下面我们来演示下。
我们分别用 make localmodconfig 和 make localyesconfig 生成两份.config配置,然后比较一下它们的区别:
看上图,所有值为m的配置项,都被修改为值为y。
其实我们除了可以用git比较这两个.config配置文件外,还可以用内核自带的 diffconfig 脚本:
这个显示的结果更简洁些。
0x09 make all[no/yes/mod/def]config / randconfig
这几个命令非常好理解,就是把所有配置项的值,都设置为 no/yes/mod/默认/随机 。
但是,我们可以通过 KCONFIG_ALLCONFIG 环境变量,为某些配置项设置为特定值。
因为这些命令都比较少使用,所以就不详细讲了。
0x0a make [yes2mod/mod2yes/mod2no]config
这几个命令也非常好理解,就是先读取当前.config配置,或者默认配置,然后把各配置项的值从 yes改成mod / mod改成yes / mod改成no。
0x0b make listnewcnofig / helpnewconfig
这两个命令是用来输出那些,在内核Kconfig中定义了,但在.config配置文件中没有为其设置值的配置项。
其实也就是我们每次更新内核代码后,新增的那些配置项。
0x0c make rust.config
除了以上讲的各种make命令外,还有一类用于生成.config配置文件的make命令,是 make *.config 这种格式,比如 make rust.config。
具体可见文章开始那个图片里的 Configuration topic targets 部分。
这类命令的作用,就是把预定义配置文件中定义的配置项,和.config文件中定义的配置项,合并在一起,最终生成一个新的.config配置文件。
下面我们来演示下。
我们先用 make defconfig 生成一份默认的.config配置,然后再执行 make rust.config:
看上图输出,该命令以.config为基础,然后合并了 kernel/configs/rust.config 文件中定义的配置项。
如果想了解这类命令的具体执行逻辑,可以看其对应的 Makefile 源码 。
0x0d 生成的.config配置文件是如何被内核使用的
其实linux内核并不是直接使用.config文件,而是使用该文件的衍生文件。
在生成.config文件时,还会同时生成其他三个文件,分别是 include/config/auto.conf,include/generated/autoconf.h,和 include/generated/rustc_cfg。
这三个文件的内容大致如下:
include/config/auto.conf 文件是在Makefile里使用。
include/generated/autoconf.h 文件是在c代码里使用。
include/generated/rustc_cfg 文件是在rust代码里使用。
这三个文件分别以不同的格式,包含了.config 中定义的所有内核配置项。
它们使得Makefile,c代码和rust代码,可以根据我们的配置,进行条件编译,甚至可以在运行时,执行不同的逻辑。
0x0e 最后
配置linux内核,是内核构建过程中最重要的一步。
只有我们深入理解内核的配置过程,熟练使用内核的配置命令,我们才能构建出一个我们想要的内核。
本篇文章几乎讲了所有的,用于配置内核的命令,并且提供了各种演示,以及阐述了各个命令的底层逻辑。
但限于篇幅原因,还是有很多细节没有讲到。
为了更好的学习和理解linux内核的配置过程,推荐大家自己动手试下以上命令。
在这一过程中,可能会遇到很多问题,这很正常。
大家可以先通过阅读内核的 官方文档,以及和配置相关的 内核源码,来尝试自己解决下。
如果还是解决不了,可以找我,我们一起来讨论下。
祝大家在学习内核的路上,更进一步。
0x0f 其他
如果有对linux及linux内核感兴趣的,可以扫描右侧二维码添加我的微信。
另外,我开设了一门 linux内核启动流程源码分析 课程,有对内核源码感兴趣的,欢迎报名。