HACK #2 如何编译Linux内核
本节介绍编译Linux内核的方法。
当发现bug而修改源代码或者添加新功能时,就需要对内核进行重新编译,生成二进制映像文件。另外,如果想要使用发布版内核中无效的功能或者驱动程序时,或者相反地,想要删除不需要的功能从而使内核更精简、更快时,或者想使用最新版的上游内核时,也需要对内核进行编译。
下面主要介绍对上游内核进行设置、编译以及安装的方法。当使用发布版内核的源码包管理系统来管理内核映像文件时,需要将内核映像文件打包。接下来以两个具有代表性的发布版Fedora和Ubuntu为例来讲解具体的方法。最后将简单地介绍在源码树外对驱动程序等进行编译的方法,以及在不同平台的编译环境编译内核的方法,即所谓的交叉编译。
内核编译的过程
对内核进行编译的步骤如下:
1.获取源代码,如有需要则进行修改。
2.设置。
3.编译。
4.根据发布版生成相应的源码包。
5.安装内核映像和模块。
使用上游内核安装内核映像时,若不使用发布版的源码包管理系统,则不需要进行步骤4。想要使用源码包管理系统来安装时,可以使用各发布版的源码包创建系统。在这种情况下步骤3和步骤4的操作是合并进行的。
下面首先讲解不使用源码包管理系统来生成、安装内核映像文件的情形。然后介绍将内核映像文件打包和安装的方法。
需要的源码包
对内核进行设置和编译时可以使用各种工具。如果没有明确指示,就不会安装表1-3和表1-4所示的源码包,但是它们是必不可少的。这些需要事先安装好。
表1-3 必要的源码包一览表(Fedora 14)
表1-4 必要的源码包一览表(Ubuntu 10.10)
编译、安装上游内核
获取源代码
关于获取内核源代码的方法,请参考Hack #1。
这里以源代码在~/linux-2.6下的情况为例。
进行设置
Linux内核自身的源代码树中就具备进行编译设置的结构,不仅可以设置编译或不编译某个功能,在进行编译时,还能非常细致地设置是将功能静态添加到Linux内核的二进制码中,还是作为模块进行编译。虽然能够进行细致的设置,但同时也造成设置项目数量过多。因此源代码树中还带有帮助进行设置的工具。这个工具包称为kconfig,应先启动这个工具再进行设置。
小贴士:2.6.38中的设置项目数量超过12 000个。
设置工具虽然准备了基于控制台(文字界面)和基于窗口(图形界面)的两种类型,但其实用户要执行的操作不管用哪一个界面都没有太大的区别。这里主要以基于控制台的工具为例展开介绍,基于窗口的工具仅在后面简单介绍。
要启动基于控制台的设置工具,需在源码树的根下执行下列命令。
$ make menuconfig
之后控制台就会显示如图1-2所示的项目。设置是按层次进行的,现在看到的是最上面一层。
在这里,先不执行任何操作,按一下【TAB】键,选择Exit后,再按下【Enter】键。就会出现询问“是否保存新设置”的选项,然后选择Yes按钮关闭设置工具。
图1-2 make menuconfig生成的设置画面
编译设置保存在源码树的根下标题为.config的文件里。因为这次是在.config文件不存在的状态下启动的设置工具,所以会生成一个默认设置内容的.config文件。
.config的内容如下所示(细节部分会因为执行环境的不同而有所差异)。
#
# Automatically generated make config: don抰 edit
# Linux/x86_64 2.6.38 Kernel Configuration
# Sun Mar 27 03:17:57 2011
#
CONFIG_64BIT=y
#CONFIG_X86_32 is not set
CONFIG_X86_64=y
CONFIG_X86=y
以#开头的行是注释行。
CONFIG_*是设置项目。这些设置项目与Linux内核的各功能相对应,编译时受这个值的控制。设置项目取表1-5所示三个值中的一个。
表1-5 设置项目(CONFIG_*)所取的值
注意事项:.config文件不能手动编辑。有时某个功能会依赖于其他功能。在这种情况下,如果设置不能正确反映依赖关系,就会出现编译错误或者最终变成无法执行的内核。kconfig可以掌控依赖关系,并保证设置的兼容性。
小贴士:在没有.config文件的情况下启动并执行make menuconfig命令后生成的.config文件,是根据kconfig的设置文件—Kconfig*中的默认值生成的。
另外,在源码树中分别为每个架构准备了默认的.config文件。不按照Kconfig的默认设置,而是想根据各架构的默认设置生成.config文件时,需执行下列命令。
$ make defconfig
各架构的默认.config文件位于以下路径。
arch/<arch>/configs/*_defconfig
现在就可以尝试进行设置了。再次执行make menuconfig命令打开设置菜单。设置菜单中经常使用到的按键如表1-6所示,以供参考。
表1-6 设置菜单的按键一览
这里以软盘驱动器为例,尝试启用它。这个设置项目在2.6.38中定义为CONFIG_BLK_DEV_FD,位于菜单层的如下位置。
Device Drivers --->
Block devices --->
Normal floppy disk support
图1-3所示就是在菜单上选择这个项目的界面。
图1-3 Normal floppy disk支持的设置
这时按【y】键,左侧的选择显示就会变成<*>。这就表示该项目已编译并静态添加到内核中。按【m】键,显示则变成。这时,该项目将作为模块编译。当使用该功能时,模块会根据需要动态添加到内核中。如果按【n】键,则会变成<>,该功能不编译。
内核的编译设置就是这样指定一个个的项目来进行的。
此外,还有一些项目需要设置数值和字符串。例如,在当前打开的界面中,“Default number of RAM disks”等就是这种项目。选择这样的项目后按回车键,就会出现对话框,可以在其中输入数值或字符串。
大致完成自己想要的设置后,可以在菜单最上层的画面上选择操作菜单的,或者连按两次【Esc】键完成设置,在出现的“是否保存新设置?”对话框中选择Yes按钮,将设置保存到.config文件中。
到这里,内核的编译设置就完成了。
小贴士:经常有人因为手头有在旧版本中生成的.config文件,而想要在此基础上进行一些修改,以编译新的内核。在这种情况下,可以将原来的.config文件复制到源码树的根下,然后执行下列命令。
$ make oldconfig
执行该命令后,就会出现一个个的对话框,询问新增加的设定项目要如何设置。如果版本之间的差别较大,就需要回答较多的项目设置问题,所以可以先针对所有的询问都按回车键,以便使用默认设置,然后再用make menuconfig等进行设置。
图1-4 make xconfig生成的设置界面
基于窗口(图形界面)的设置工具通过下列命令来启动。
$ make xconfig
启动后就会打开如图1-4所示的窗口,就在这里进行设置。
如果掌握了基于控制台的设置工具,那么基于窗口的工具也就很容易掌握了。可以根据个人喜好来选择使用哪一个。
进行编译
要对已完成设置的上游内核进行编译,需在源码树的根目录下执行下列命令。
$ make
编译需要花费的时间与机器的性能和设置有较大的关系,快的时候只需要几分钟,有时也可能需要花费几个小时。请耐心等待。
如何节约编译时间
下面列出了几个缩短编译时间的小提示。
在内核的设置上下工夫
编译的代码越少,编译就能越快完成。一般为了使发布版内核在多样的环境中能够顺利运行,都会带有多个驱动程序。将其中在自己的环境中不需要的驱动程序设置为无效,仅将必要的作为编译对象,就能大幅缩短编译时间。关于设置的技巧请参考Hack #6。
使用Make的-j选项
-j选项是用来指定make的并发性的选项。并发性是用数值来指定的,例如“-j 4”。当机器为多处理器时,可以按照处理器的数量来指定数值,这样就有可能实现快速编译。
购买高性能的机器
速度是永远的追求。
将内核安装到系统中
编译完成后,就可以将生成的内核安装到系统中。安装时必须有root权限。
安装分为两个阶段进行。第一阶段是模块的安装。在编译完成的源码树的根目录下执行下列命令。
# make modules_install
这时,编译后的模块就安装到/lib/modules下。
第二阶段是安装内核二进制映像文件,生成并安装boot初始化文件系统映像文件。同样也是在源码树的根目录下执行下列命令。
make install
这时,内核映像文件就安装到/boot下。如果使用的是Fedora系列的发布版,就会同时生成boot初始化文件系统映像,并同样安装到/boot下。而Ubuntu等Debian系列的发布版则需要执行下列命令,另行生成和安装boot初始化文件系统映像。在<内核版本>的部分请输入表示当前生成的内核版本的文字。
# update-initramfs 朿 杒 <内核版本>
表1-7所示为通过安装生成的文件以及目录的一览表。在<内核版本>的部分中同样输入表示当前生成的内核版本的文字。
表1-7 通过安装内核而生成的文件和目录
在一些系统设置下可能需要手动进行GRUB设置才能从当前安装的内核启动。在这种情况下,请适当地编辑/boot/grub/menu.lst或者执行update-grub命令。
到这里内核的安装就完成了。这时请重启机器,确认新的内核是否能够正常运行。
注意事项:在确认新内核能够正常运行之前,绝对不要删除现在所使用的内核以及相应的GRUB的记录。新内核经常会出现无法启动的情况。这时如果没有启动的内核,系统就无法进行操作。
小贴士:内核二进制映像的文件名和模块目录名是根据内核的版本来命名的。因此,当对已安装版本的内核进行再次编译并安装时,原来安装的内核以及模块会被覆盖。
为了防止这种情况的发生,可以在设置项目CONFIG_LOCALVERSION中设置文字。在这个设置项目中指定的文字会作为内核版本的一部分,因此可以防止被覆盖。
其他的make对象或变量
在进行内核的设置或编译时,有一些可用的make目标,如表1-8所示。
表1-8 其他的make对象
表1-9展示了为控制make的输出而设置的make变量。这些变量需在对象前指定,如:
$ make V=1 clean
表1-9 控制make输出的变量
卸载内核
按照前面介绍的安装方法安装的内核不在发布版源码包管理系统的管理范围内。因此不能用rpm或dpkg这样的命令来卸载。
但是也不需要担心,内核的文件配置如表1-5所示,相对来说比较简单。想要卸载时,只需要删除这些文件就可以了。
注意事项:卸载内核时请慎重考虑。另外,请一定要做好更改GRUB设置等工作。
生成内核包
Fedora
Fedora的源码包管理系统是RPM。要将内核纳入RPM的管理范围内,就需要生成RPM源码包。
其实Linux内核在创建时就具备生成RPM源码包的功能。编译时需要将rpm-pkg作为对象执行make命令。
$ make rpm-pkg
通过这条命令,编译内核后就会创建源码包(SRPM)和二进制码包(RPM),二进制码包存放在~/rpmbuild/rpms下,源码包存放在~/rpmbuild/SRPMS下。
如果拥有将SRPM解压缩后的发布版内核的源码,则使用rpmbuild创建源码包。如果内核的SRPM是解压缩到~/rpmbuild下的,则执行下列命令创建源码包。
$ rpmbuild 朾a ~/rpmbuild/SPECS/kernel.spec
所创建的源码包存放的目录与上面相同。这些源码包和普通源码包一样,可以使用rpm命令来安装、卸载。
小贴士:在上游内核中创建源码包时也是用make来调出rpmbuild的。
Ubuntu
Ubuntu的源码包管理系统是dpkg。源码包为deb格式。
上游内核的创建与RPM同样,也能生成deb源码包。这一make操作的对象为deb-pkg。通过执行下列make命令,就能够创建deb源码包。
$ make deb-pkg
所创建的源码包存放在源码树的根目录下。会生成数个源码包,其中包含内核映像和模块的是linux-image-<内核版本>.deb文件。这些源码包的操作和普通的deb源码包文件一样,可以用dpkg来进行。
此外,Ubuntu还在kernel-package包里收录了用来协助内核包创建的工具—make-kpkg命令。这个工具可以通过命令选项对创建操作进行设置,根据需要也可以使用这个工具。这里就不介绍详细的使用方法了。
在源码树外编译模块
有时可能想要对还未导入上游内核的驱动程序等与内核源码树分开提供的源代码进行编译,并将其作为模块安装。
在这种情况下,只要驱动程序源代码的Makefile编写正确,就可以按照下列方法进行编译、安装。
$ make 朇 /lib/modules/$(uname -r)/build M=$PWD
# make 朇 /lib/modules/$(uname -r)/build M=$PWD modules_install
当为make指定-C选项时,make首先会读取指定目录下的Makefile。这里指定为-C选项的变量,是指向当前正在运行的内核源目录的符号链接。也就是说,这个驱动程序与当前运行的内核是在完全相同的环境下创建,具体来说,就是使用头文件或.config文件来创建的。
指定M=$PWD是为了告知Linux内核的创建操作正在源码树外执行创建操作。
交叉编译内核
交叉编译是指针对与正在执行编译的平台不同的其他平台生成二进制数据。例如,在x86_64环境下生成针对ARM的二进制数据的情形。这种编译器又称为“交叉编译器”。
只要拥有交叉编译器,对Linux内核进行交叉编译就变得非常简单。这时还需要为make赋予两个变量,如表1-10所示。
表1-10 交叉编译所需的变量
举一个使用交叉编译器armv5tel-linux-gcc来交叉编译ARM内核的例子。在这种情况下,make命令变成如下所示的内容。
ARM内核的二进制映像较多使用的是uImage格式。第一行创建这个格式的二进制映像,第二行创建模块。
$ make ARCH=arm CROSS_COMPILE=armv5tel-linux- uImage
$ make ARCH=arm CROSS_COMPILE=armv5tel-linux- modules
创建的内核二进制映像作为源码树内的arch/arm/boot/uImage文件。
创建的内核和模块必须转移到对象机器上。如果在对象机器上可以使用源码包管理系统,则最简单的方法就是生成源码包并在对象机器上安装。然而,如果不能使用源码包管理系统,虽然内核映像转移起来很简单,但是模块就有一些问题。模块分散在源码树的各个目录下,想要手动查找这些模块并在/lib/modules下构建目录树,是不太现实的。
其实,通过modules_install安装模块的位置可以用变量INSTALL_MOD_PATH来指定。可以利用这一点,例如,当安装在主目录下时,可以用tar对每个目录进行整合,再转移到对象机器上。这一操作可以用下列命令来实现。
$ make ARCH=arm CROSS_COMPILE=armv5tel-linux- INSTALL_MOD_PATH=~/armroot-2.6.38 modules_install
这样就会在主目录下生成一个标题为~/armroot-2.6.38/lib/modules的目录,模块就安装在这个目录下。
模块的目录下有标题为build和source的符号链接,这些都是指向编译过内核的源码树。如果在对象机器上完全不进行编译,就不需要进行修改,如有必要可以在对象机器上适当修改。
小结
本节主要以上游内核为中心,讲解了对内核进行编译设置、编译、安装的方法。一方面,内核是系统的根源,如果设置或者安装错误,系统就有可能陷入无法启动的危险中。另一方面,由于基本不依赖于其他的软件,并且可以安装多个内核,因此笔者认为可以比较放心地尝试修改或改造。
为了保证系统的流畅性,也为了尽快使用最新的内核,不仅内核开发人员,而且Linux系统的普通用户也非常需要掌握内核的编译、安装方法。你也可以挑战一下。
参考文献
Documentation/kbuild/*(内核源文档)
—Munehiro IKEDA