《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #2 如何编译Linux内核

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

时间: 2024-10-24 18:45:53

《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #2 如何编译Linux内核的相关文章

《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #1 如何获取Linux内核

HACK #1 如何获取Linux内核 本节介绍获取Linux内核源代码的各种方法. "获取内核"这个说法看似简单,其实Linux内核有很多种衍生版本.要找出自己想要的源代码到底是哪一个,必须首先理解各种衍生版本的意义. 接下来将简单介绍Linux内核的开发模式,并分析各种衍生版本在其中所处的地位,然后介绍获取这些衍生版本的源代码的方法. 内核的种类 想要获取正确的Linux内核源代码,首先必须了解Linux内核的开发模式. Linux内核是由多个开发者以分散型的模式进行开发的.这里出

《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #15 ramzswap

HACK #15 ramzswap 本节介绍将一部分内存作为交换设备使用的ramzswap. ramzswap是将一部分内存空间作为交换设备使用的基于RAM的块设备.对要换出(swapout)的页面进行压缩后,不是写入磁盘,而是写入内存.可以使用的内存仅为完成压缩的部分.压缩处理使用的是LZO注1. ramzswap是从Linux 2.6.33合并到Staging驱动程序的.Staging驱动程序是指尚未达到某种程度的质量的试验性驱动程序. 通过使用ramzswap,运转速度可以比换出到一般磁盘

《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #3 如何编写内核模块

HACK #3 如何编写内核模块 本节将介绍向Linux内核中动态添加功能的结构-内核模块的编写方法. 内核模块 Linux内核是单内核(monolithic kernel),也就是所有的内核功能都集成在一个内核空间内.但是内核具有模块功能,可以将磁盘驱动程序.文件系统等独立的内核功能制作成模块,并动态添加到内核空间或者删除. 内核模块是可以动态添加到Linux内核空间的二进制文件,文件扩展名为ko. 内核模块的编写方法大致有两种.一种是将内核源码树带有的功能编写为模块的方法(参考Hack #2

《Linux内核精髓:精通Linux内核必会的75个绝技》一导读

前 言 内核是操作系统的核心,操作系统的基本功能都是由内核提供的.文件生成和数据包传输等也是通过内核的功能实现的.但这些都不是简单的任务.平时可能意识不到,但这其中确实包含了很多先进技术.例如,在文件系统方面,配置文件时尽量减少磁盘扫描,在网络方面,由于路由表的入口数量庞大,因此设计时尽量保证对系统整体影响较小的设计.在内存管理.进程管理方面也作出了很多努力.解读这种先进技术也是内核构建的魅力之一. 然而,最近的Linux所提供的并不只有基本功能.随着功能的不断发展,现在已经出现了很多特定领域的

《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #13 使用Block I/O控制器设置I/O优先级

HACK #13 使用Block I/O控制器设置I/O优先级 本节介绍使用Block I/O控制器的功能设置I/O优先级的方法.Block I/O控制器可以将任意进程分组,并对该分组设置I/O的优先级.这个功能是在Linux 2.6.33时添加到Linux内核中的.例如,在前台进行一般处理的同时,在后台磁盘备份处理的情况下,如果备份处理频繁地向磁盘进行I/O操作,前台的处理即使有I/O请求,也不能立刻进行I/O处理,结果导致前台处理的性能下降.Block I/O控制器在这种情况下就非常有效.创

《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #11cpuset

HACK #11cpuset 本节介绍控制物理CPU分配的cpuset.cpuset是Linux控制组(Cgroup)之一,其功能是指定特定进程或线程所使用的CPU组.另外,除CPU以外,同样还能指定内存节点的分配.以前的内核具有CPU affinity功能,该功能将线程分配给特定CPU.现在的内核中虽然也有affinity(taskset命令),但推荐使用cpuset.用法使用cpuset前,必须通过内核config启用cpuset功能.CONFIG_CPUSETS=y最近的发布版在标准中就已

《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #8 调度策略

HACK #8 调度策略 本节介绍Linux的调度策略(scheduling policy). Linux调度策略的类别大致可以分为TSS(Time Sharing System,分时系统)和实时系统这两种. 一方面,一般的进程是通过分时运行的.也就是说,使用CPU的时间达到分配给进程的时间(时间片)时,就会切换到其他进程.这种分时运行的调度策略称为TSS. 另一方面,在实时制约较严格且要求保证实时的处理中,就需要指定静态的执行优先级,并严格按照执行优先级进行调度.对这种对应答性有要求的进程,可

《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #4 如何使用Git

HACK #4 如何使用Git 本节介绍Git的使用方法. Git是Linux内核等众多OSS(Open Source Software,开源软件)开发中所使用的SCM(Source Code Management,源码管理)系统.在2005年以前,在Linux内核开发中一直使用一个叫做BitKeeper的SCM.但是由于后来BitKeeper的许可证被更改,可能会对开发造成障碍,因此Linux不得不改用新的SCM进行开发.在这种情况下,Linux内核的创始人Linus Torvalds就开发了

《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #17 如何使用ext4

HACK #17 如何使用ext4 本节介绍ext4的编写和挂载方法.开发版ext4的使用方法. ext4是ext3的后续文件系统,从Linux 2.6.19开始使用.现在主要的发布版中多数都是采用ext4作为标准文件系统. 除了间接参照块管理以外,ext4还以扩展形式支持块的管理,使其能够处理更大的文件.文件系统.另外,还增加了确保多块(multiblock)注1.确保延迟块.提高fsck速度.碎片整理等新的功能.在ext3中,时间戳(time stamp)的单位为毫秒,而ext4中变成了纳秒