[Linux]记录一次内核编译过程

记录一次内核编译过程

我觉得大概没有什么新手踩的坑比我这两天踩的还多了… (微笑)

1. 哪里下内核源码?

Linux内核官网 kernel.org

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-toolspkg-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.hcompiler-gcc4.h 头文件可以分别给 GCC 3GCC 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 未打开导致,按如下操作可以设定 EXPERIMENTALy
General setup -> 打开 Prompt for development and/or incomplete code/drivers
(为什么是这个?)之后再打 make dep 便没有依赖警告。打开 EXPERIMENTAL 的后果是,允许在系统中使用实验性的模块,可能有系统不稳定的风险。

2.1.坑2.1 已知一个配置名,在 menuconfig 中找到开关

已知配置名 EXPERIMENTAL ,用如下步骤可以在配置文件 .config 中找到配置定义的位置:

  1. 打开配置文件
vim .config
  1. 搜索配置名
    vim 中敲打 /EXPERIMENTAL<回车>,可以发现配置名全称为 CONFIG_EXPERIMENTAL
  2. 定位
    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 DriversMultifunction 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.cipt_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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值