记录一次内核编译过程
我觉得大概没有什么新手踩的坑比我这两天踩的还多了… (微笑)
1. 哪里下内核源码?
1.1 怎么操作
在官网上点开HTTP的链接:https://www.kernel.org/pub/ 。
然后依次打开linux,kernel,选择版本目录,这里我选择 v2.6,然后选择 linux-2.6.39.tar.xz
开始下载。
1.2 解压到哪
下载得到的是 .tar.xz
格式的压缩包。
解压后可以选择放在 /usr/src
目录下。
最后得到 linux-2.6.39
文件夹及其下内核源码。
2. 怎么编译
编译之前需要配置内核,选择哪些部分编译到内核里,哪些部分编译成动态模块,哪些不编译,之后会生成配置文件 .config
。再然后才可以进行编译。这里笔者在 Ubuntu 16.04
下进行编译。
2.1 怎么配置
在 linux-2.6.39
目录下打开终端。
这里有多种配置方式,大致如下
make config # 方法一,最啰嗦的配置方式
make xconfig # 方法二,用桌面窗口程序配置
make menuconfig # 方法三,用图形终端配置
方法一不需要其他依赖。
方法二需要安装 qt4-dev-tools
, pkg-config
等。
方法三需要安装 libncurses5
。
这里笔者用方法三:
sudo apt install libncurses5
完了以后可以在源码根目录下打
make menuconfig
编译出终端图形界面并运行。
然而笔者一开始的这步就出现错误了,估计大家也差不多是这样的,可以参考一下 2.1.坑1 GCC 版本过高
的内容来解决。
界面上有很多选项可以做选择,设置为 *
则表示编译到内核中,设置为 M
则表示编译成模块以动态加载。
初学者的话可以不做改动,直接用默认配置就可以了,保存然后退出。这时便生成了 .config
配置文件。
最后打
make dep
重新生成内核构建的依赖树,如果没有错误那就可以开始下一步的编译了。
然而笔者发现这个默认配置居然有依赖警告。依赖错误请看 2.1.坑2 解决依赖警告
。
2.1.坑1 GCC 版本过高
然而实际操作过程并没有如 2.1
所述顺利,打 make menuconfig
一上来就给了个找不到头文件的错误
include/linux/compiler-gcc.h:90:30: fatal error: linux/compiler-gcc5.h: 没有那个文件或目录
compilation terminated.
错误原因是编译器版本过新,这个内核只有 compiler-gcc3.h
, compiler-gcc4.h
头文件可以分别给 GCC 3
和 GCC 4
使用,笔者用的编译器是 GCC 5
,需要切换使用旧版本的编译器(网上有人给内核添加 compiler-gcc5.h
头文件,可能也是可以的)。
笔者决定用 GCC 4.7
,用如下命令安装 GCC 4.7
:
sudo apt install gcc-4.7
安装完了以后用命令 gcc --version
检查 GCC
版本,会发现编译器还是新版本的。
原因是 gcc
实际上是个指向 gcc-5
或其他原来就安装了的 GCC
的软链接。
我们需要把 gcc
从原来的指向 gcc-5
改为指向 gcc-4.7
。从而达到命令 gcc
运行的是旧版本的 GCC
的目的。
使用命令
which gcc
可以看到 gcc
的绝对路径,一般为 /usr/bin/gcc
,使用命令
ls -l /usr/bin/gcc
可以显示这个软链接实际指向的文件
lrwxrwxrwx 1 root root 5 4月 28 19:53 /usr/bin/gcc -> gcc-5
打
gcc-4.7 --version
可以看到,也并确认 GCC 4.7
已安装,然后可以打下面命令删除旧软链接,创建新软链接
sudo rm /usr/bin/gcc
sudo ln -s /usr/bin/gcc-4.7 /usr/bin/gcc
之后打
gcc --version
检查 GCC
的版本,可以发现就是旧版本的 GCC 4.7
。
2.1.坑2 解决依赖警告
scripts/kconfig/conf --silentoldconfig Kconfig
warning: (GFS2_FS) selects DLM which has unmet direct dependencies (EXPERIMENTAL && INET && SYSFS && CONFIGFS_FS && (IPV6 || IPV6=n))
warning: (IMA) selects TCG_TPM which has unmet direct dependencies (HAS_IOMEM && EXPERIMENTAL)
warning: (SCHED_AUTOGROUP) selects CGROUP_SCHED which has unmet direct dependencies (CGROUPS && EXPERIMENTAL)
warning: (ACPI_HOTPLUG_CPU) selects ACPI_CONTAINER which has unmet direct dependencies (ACPI && EXPERIMENTAL)
warning: (GFS2_FS) selects DLM which has unmet direct dependencies (EXPERIMENTAL && INET && SYSFS && CONFIGFS_FS && (IPV6 || IPV6=n))
warning: (IMA) selects TCG_TPM which has unmet direct dependencies (HAS_IOMEM && EXPERIMENTAL)
warning: (SCHED_AUTOGROUP) selects CGROUP_SCHED which has unmet direct dependencies (CGROUPS && EXPERIMENTAL)
warning: (ACPI_HOTPLUG_CPU) selects ACPI_CONTAINER which has unmet direct dependencies (ACPI && EXPERIMENTAL)
*** Warning: make dep is unnecessary now.
起初看到这个的时候真是日了狗了,这默认配置居然有警告??这对新手也太不友好了吧。
这个依赖警告是指配置的选项有自相矛盾的地方,说明我们要好好重新更改配置才行。
这些警告都是由于 EXPERIMENTAL
未打开导致,按如下操作可以设定 EXPERIMENTAL
为 y
General setup
-> 打开 Prompt for development and/or incomplete code/drivers
(为什么是这个?)之后再打 make dep
便没有依赖警告。打开 EXPERIMENTAL
的后果是,允许在系统中使用实验性的模块,可能有系统不稳定的风险。
2.1.坑2.1 已知一个配置名,在 menuconfig 中找到开关
已知配置名 EXPERIMENTAL
,用如下步骤可以在配置文件 .config
中找到配置定义的位置:
- 打开配置文件
vim .config
- 搜索配置名
vim
中敲打/EXPERIMENTAL<回车>
,可以发现配置名全称为CONFIG_EXPERIMENTAL
。 - 定位
CONFIG_EXPERIMENTAL
正好在菜单General setup
下第一个,
...
#
# General setup
#
CONFIG_EXPERIMENTAL=y
CONFIG_INIT_ENV_ARG_LIMIT=32
...
我们可以在 menuconfig
中找菜单 General setup
下的第一个项,即为 Prompt for development and/or incomplete code/drivers
。
2.2 开始编译
内核编译的结果是一个映像文件
在 linux-2.6.39
路径下,使用如下命令即可以开始编译
make bzImage
如果电脑用的 CPU
是多核的,还可以改用如下命令多线程编译
make bzImage -j4
其中的 4
可以换成 CPU
的核心数量。
编译过程中应该会有 SECTION MISMATCH
警告,所以建议一开始就打开输出这个调试信息的开关,不然等警告出现,想排查时才打开开关会发现“握草居然要重新编译”。
make bzImage -j4 CONFIG_DEBUG_SECTION_MISMATCH=y
编译过程时间很长,笔者的电脑很菜,要花个半个钟到一个钟时间。期间可以补个番,CPU强劲的可以补泡面番,然后回来发现“握草居然编译出错了”(雾)。
编译时也可以注意一下输出,你会发现“这什么鸟人,写的程序一堆变量未使用的警告”(大雾)。
编译过程很大几率真的会出问题,笔者遇到的问题如下:
2.2.坑1 Perl 版本过高
如果 Perl
版本过高,编译过程中还会出现错误
Can't use 'defined(@array)' (Maybe you should just omit the defined()?) at kernel/timeconst.pl line 373.
此时修改文件 kernel/timeconst.pl
文件的第373行的代码即可,
将
if (!defined(@val)) {
改为
if (!(@val)) {
2.2.坑2 Ctrl + C 暂停编译导致部分ELF文件错误
笔者编译过程中曾使用 Ctrl + C
暂停编译过程,结果再编译时出现了 ELF
文件错误的错误。
这错误的文件一般都是 .o
文件,处理方法就是将这个 .o
文件删除,然后再继续编译即可。
原因是 Ctrl + C
中止了 GCC
编译源文件并输出 .o
文件的过程,导致 .o
文件内容不完整。
暂停编译应该使用 Ctrl + Z
。
2.2.坑3 不明MODPOST警告
编译过程的 MODPOST
会出现大量的警告
下面是任意几个
WARNING: vmlinux.o(.devinit.text+0x683d): Section mismatch in reference from the function device_bk_init.isra.1() to the (unknown reference) .init.data:(unknown)
The function __devinit device_bk_init.isra.1() references
a (unknown reference) __initdata (unknown).
If (unknown) is only used by device_bk_init.isra.1 then
annotate (unknown) with a matching annotation.
WARNING: vmlinux.o(.devinit.text+0x700a): Section mismatch in reference from the function device_8607_init() to the variable .init.data:onkey_devs
The function __devinit device_8607_init() references
a variable __initdata onkey_devs.
If onkey_devs is only used by device_8607_init then
annotate onkey_devs with a matching annotation.
这个警告在百度必应怎么找都找不到解决方案,特别是这个短语 matching annotation
根本没有头绪。被这个函数device_8607_init恶心了好久,这里我也不给这个函数加反引号了,恶心这个函数一下。
于是最终打算把这个链接错误的模块(怎么找到是这个模块的?)禁用掉:
打开编译选项
make menuconfig
进入 Device Drivers
, Multifunction device drivers
,关闭 Support Marvell 88PM8606/88PM8607
。
然后重新(继续)编译
make bzImage -j4
就没有这个警告了。
2.2.坑3.1 在内核的源码中找一个函数
在源码目录下用
grep 'device_8607_init' . -rnI
其中 device_8607_init
可换成要找的函数名。
grep
的具体使用说明,可以自行查阅相关文档。
一段时间后会输出这个字符串表达式在文件中的位置。
./drivers/mfd/88pm860x-core.c:711:static void __devinit device_8607_init(struct pm860x_chip *chip,
./drivers/mfd/88pm860x-core.c:786: device_8607_init(chip, chip->client, pdata);
./drivers/mfd/88pm860x-core.c:797: device_8607_init(chip, chip->companion, pdata);
./System.map:23214:ffffffff815418d5 t device_8607_init
然后可以打
vim drivers/mfd/88pm860x-core.c +711
在源码中找到并定位到这个函数。
2.2.坑3.2 在 menuconfig 中找一个配置
打开 menuconfig
后,可以按 /
打开搜索窗口,输入名称查找配置。
2.2.坑4 生存周期的MODPOST警告
其实笔者起初使用 GCC 4.9
编译的,期间有生存周期的警告,提示缺少 __devinit
之类的。开始笔者尝试过修改源码,虽然能解决一时的问题,但是换个全新的代码就又会又会有警告。后来再次编译时,这个警告不清楚为什么就没有了。
根据 Stack Overflow 上的说法,不建议修改内核源码,而是通过修改配置来解决问题。
所以不修改源码的解决方法的线索如下:
- 不用
GCC 4.9
而改用GCC 4.7
- 开始编译前用
make dep
重新生成依赖树,并确保没有依赖错误
2.2.坑5 Windows 不区分文件名大小写导致文件被覆盖
*** No rule to make target 'net/ipv4/netfilter/ipt_ecn.c', needed by 'net/ipv4/netfilter/ipt_ecn.o'
在 Windows
下解压缩内核源码压缩包期间会出现文件替换的提示,这是因为辣鸡内核里有名字中只有大小写不同的文件 ipt_ecn.c
和 ipt_ECN.c
,这在 Windows
下是禁忌。
解决方法是将源码在 Linux
等区分大小写的系统下解压缩即可。
3. 怎么运行
TODO: 咕咕咕
4. 编译的正确姿势
都说 Linux
首先是适配 x86
平台,我这踩了怎么多坑,怎么回事?
原来这个默认配置不是在 x86
上编译的正确姿势,设置 x86
的默认配置使用如下命令
make i386_defconfig
之后会提示配置已写入 .config
。
之后再编译就不会有大问题了。(狗头)
内核对很多指令集都有一套默认配置,具体可以用命令搜索查看:
find -name "*_defconfig"
4.1 编译命令记录
cd linux-2.6.39
make i386_defconfig
make menuconfig
make bzImage
make modules