第2章从内核出发
介绍Linux内核的一些基本知识:从何处获取源代码,如何编译源代码,如何安装新内核。考察一下内核程序与用户空间程序的差异,以及内核中所使用的通用编程结构。内核在很多方面有其独特性,但从现在来看,内核和其他大型软件项目并没有多大差别。
2.1 获取内核源代码
Linux内核官网http://www.kernel.org,可以获取当前版本的Linux源代码,可以是完整的压缩形式(使用tar命令创建的一个压缩文件),也可以是增量补丁形式。
1、使用Git
建议使用Git来下载和管理Linux内核源代码。可以使用Git来获取最新提交到Linus版本树的一个副本:
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
当下载代码后,可以更新你的分支到Linus的最新分支:
git pull
2、安装内核源代码
内核压缩以GNU zip(gzip)和bzip2两种形式发布。bzip2是默认和首选形式,因为它比gzip更有优势。以bzip2形式发布的Linux内核叫做linux-x.y.z.tar.bz2,这里x.y.z是内核源码的具体版本。下载源代码之后,对其解压。
如果压缩形式是bzip2,则运行:
tar xvjf linux-x.y.z.tar.bz2
如果压缩形式是GNU的zip,则运行:
tar xvzf linux-x.y.z.tar.gz
解压后的源代码位于linux-x.y.z目录下。如果使用git获取和管理内核源代码,不需要下载压缩文件,只要运行git clone命令,git就会下载并且解压最新的源代码。
3、何处安装并触及源代码
内核源码一般安装在/usr/src/linux目录***意:不要把这个源码树用于开发,因为编译你的C库所用的内核版本就链接到这颗树。不要以root身份对内核进行修改,而应当建立自己的主目录,仅以root身份安装新内核。即使在安装新内核时,/usr/src/linux目的都应当原封不动。
4、使用补丁
在Linux内核社区中,补丁是通用语。可以以补丁的形式发布对代码的修改,也可以以补丁的形式接收其他人所做的修改。增量补丁可以作为版本转移的桥梁。不再需要下载内核源码的全部压缩,而只需给旧的版本打上一个增量补丁。这不仅节约带宽,还省时间。注意:要应用增量补丁,从内核源码树开始,只需运行:
patch -p1 < ../patch-x.y.z
一般来说,一个给定版本的内核补丁总是打在前一个版本上。
2.2 内核源码树
内核源码树有很多目录组成,而大多数目录又包含更多的子目录。源码树的根目录及其子目录如表2-1所示:
表2-1内核源码树的根目录描述
备注:
COPYING文件是内核许可证。CREDITS是开发了很多内核代码开发者列表。MAINTAINERS是维护者列表,负责维护内核子系统和驱动程序。Makefile是基本内核的Makefile。
2.3编译内核
2.6内核提供了一套新工具,使得编译内核更加容易。
1、配置内核
在编译Linux内核之前可以配置和定制。可以把自己需要的特定功能和驱动程序编译进内核。编译内核之前,必须配置内核。由于内核提供了许多的功能,支持了很多硬件,因而有许多东西需要配置。可以配置的各种选项,以CONFIG_FEATURE形式表示,其前缀为CONFIG。例如,对称多处理器SMP的配置选项为CONFIG_SMP。如果设置了该选项,则SMP启用,否则,SMP不起作用。配置选项既可以用来决定哪些文件编译进内核,也可以通过预处理命令处理代码。
这些配置项要么是二选一,要么是三选一。二选一就是yes或no。例如CONFIG_PREEMPT就是二选一,表示内核抢占功能是否开启。三选一可以是yes、no或module。module意味着该配置项被选定了,但编译时这部分功能的实现代码以模块(一种可以动态安装的独立代码段)的形式生成的。在三选一的情况下,yes选项表示把内核代码编译进主内核镜像中,而不是作为一个模块。驱动程序一般都用三选一的配置项。
配置选项也可以是字符串或整数。这些选项并不控制编译过程,而只是指定内核源码可以访问的值,一般以预处理宏的形式表示。比如,配置选项可以指定静态分配数组的大小。
销售商提供的内核,其发布版中包含了预编译的内核,使得所需的功能得以充分地启用,并计划把所有的驱动程序都编译成模块。这就为大多数硬件作为独立的模块提供了坚实的内核支持。
内核提供了各种不同的工具来简化内核配置。最简单的一种是一个字符界面下的命令行工具:
make config
这个工具会逐一遍历所有配置项,要求用户选择yes、no或是module(如果是三选一的话)。这个过程要耗费掉很长时间,所以,应该多利用基于ncurse库编制的图形界面工具:
make menuconfig
或者,是用基于gtk+的图形工具:
make gconfig
这三种工具将所有的配置项分门别类放置,比如按照处理器类型和特点。可以按类移动、浏览内核选项,当然也可以修改其值。
这条命令会基于默认的配置为体系结构创建一个配置:
make defconfig
尽管这些缺省值有点随意性,但是,如果从未配置过内核,那会提供一个开端。运行这个命令,然后看看,确保为你的硬件所配置的选项是启用的。
这些配置项会被存放在内核代码树根目录下的.config文件中,并且可以直接修改它。在修改过配置文件之后,或者在用已有的配置文件配置新的代码树时,应该验证和更新配置。
make oldconfig
在编译内核之前都应该这么做。
配置选项CONFIG_IKCONFIG_PROC把完整的压缩过的内核配置文件存放在/proc/config.gz下,这样当编译一个新内核时就可以方便地克隆当前的配置。如果内核已经启用了此选项,就可以从/proc/下复制出配置文件并且使用它来编译一个新内核:
zcat /proc/config.gz > .config
make oldconfig
一旦内核配置好了,就可以使用一个简单的命令来编译内核了:make
这跟2.6以前的版本不同,不用在每次编译内核之间都运行make dep了——代码之间的依赖关系会自动维护。也无须再指定像老版本中bzImage的编译方式或独立地编译模块,默认的Makefile规则会打点这一切。
2、减少编译的垃圾信息
如果想尽量少地看到垃圾信息,却又不希望错过错误报告与警告信息的话,可以用以下命令来对输出进行重定向:
make > ../detritus
需要查看编译的输出信息,可以查看这个文件。因为错误和警告都会在屏幕上显示,需要看这个文件的可能性不大。输入如下的命令:
make > /dev/null
可把无用的输出信息重定向到/dev/null。
3、衍生多个编译作业
make程序能把编译过程拆分成多个并行的作业。其中的每个作业独立并发地运行,这有助于加快多处理器系统上的编译过程,也有利于改善处理器的利用率,因为编译大型源代码树也包括I/O等待所花费的时间。
默认情况下,make只衍生一个作业,因为Makefiles常会出现不正确的依赖关系。对于不正确的依赖,多个作业可能会互相踩踏,导致编译过程出错。当然,内核的Makefiles没有这样的编码错误,因此衍生出的多个作业编译不会出现失败。为了以多个作业编译内核,使用如下命令:
make -jn
注意:n是要衍生出的作业数。实际上,每个处理器上一般衍生出一个或两个作业。例如,在一个16核处理器上,可以输入如下命令
make -j32 > /dev/null
利用出色的distcc或者ccache工具,也可以动态地改善内核的编译时间。
4、安装新内核
内核编译好之后,需要安装。怎么安装就和体系结构以及启动引导工具BootLoader有关-查阅启动引导工具的说明,按照其指导将内核镜像拷贝到合适的位置,并且按照启动要求来进行安装。一定要保证随时有一个或两个可以启动的内核,以防新编译的内核出现问题。
例如,在使用Grub(引导程序)的x86系统上,可能需要把arch/i386/boot/bzImage拷贝到/boot目录下,像vmlinuz-version这样命名它,并且编辑/etc/grub/grub.conf文件,为新内核建立一个新的启动项。使用LILO启动的系统应当编辑/etc/lilo.conf,然后运行lilo。
模块的安装是自动的,也是独立于体系结构的。以root身份运行,只要运行:
make modules_install
就可以把所有已编译的模块安装到主目录/lib/modules下。
编译时会在内核代码树的根目录下创建一个System.map文件。这是一份符号对照表,用以将内核符号和它们的起始地址对应起来。调试时,如果需要把内存地址翻译成容易理解的函数名以及变量名,这会很有用。