也谈Linux Kernel Hacking – Kconfig与Kbuild

挖掘简单现象背后的复杂本质。– Tony Bai^_^

 
上文讲到Linux Kernel的配置和编译十分简单,甚至简单到可以与一个用户层应用相媲美。这一切都是因为Linux Kernel实现了一套易于使用、变更和后期维护的配置和编译体系。要知道最新Linux Kernel版本的代码量可是千万级别的,并且模块众多,其背后的配置和编译体系一定不那么简单,这次我们就来尝试Hack一下这套体系。
 
作为操作系统内核级系统软件,Linux Kernel在设计配置和编译体系时至少应该有如下几点考虑:
* 满足配置和编译内核以及内核模块的所有需求
* 较高的运行效率
* 配置阶段和编译阶段平滑结合
* 对内核开发者来说,这套体系应该易用、易变、易维护
* 其设计本身应该做到层次清晰
 
从配置和编译Linux Kernel所使用的命令来看,Linux Kernel的配置和编译体系总体上还是基于GNU Make的,没有另外使用其他的编译工具(比如SconsCMake等)。但Linux Kernel实现了Kconfig和Kbuild,用于辅助内核的配置和编译。
 
Kconfig,顾名思义,用于辅助2.6以后版本Linux内核的配置(Kernel config);Kbuild,也物如其名,用于辅助2.6以后版本Linux内核的编译(Kernel build)。这里索性将Kconfig和Kbuild称作辅助工具(不单纯叫脚本或配置文件),因为它们自身既有逻辑概念,又有物理存在。如果你曾在Linux Kernel的源码目录中徜徉过,你就会知道Kconfig文件散布在核心源码的各个角落;Kbuild文件还好,只在顶层目录、include目录下子目录、drivers下子目录以及各个arch/$ARCH/include的子目录中分布。
 
如果Linux Kernel没有引入Kconfig和Kbuild,那么本篇文章就没有了存在的必要性 – Makefile纵使数量众多,也是可以慢慢消化的,毕竟Make的规则就那么多。但Kconfig和Kbuild的引入好似为Linux Kernel配置和编译引入了一层抽象,对外(Linux核心开发者)确实是简单了,但对于我这样的要Hack编译体系的人来说,这层抽象本身就具有一定复杂性,势必需要耗时耗力地去理解。下面我们就来结合不同阶段的使用场景来深入理解一下Kconfig和Kbuild。
 
一、make *config阶段
Linux Kernel的配置项都存储于散布在Kernel源码各处的Kconfig文件中。Kconfig文件之于Linux Kernel就好比configure.ac或Makefile.am之于那些使用autotools作为构建工具的用户层应用,至少设计思路是类似的 – 先config,再make;make阶段会利用config阶段生成的一些文件。
 
Kconfig既是配置文件,也是一种配置语言,可以理解为是一种针对Linux Kernel配置的领域特定语言(DSL)。Documentation/kbuild/kconfig-language.txt文件对该种配置语言做了详尽的使用说明,这里就不做赘述了。我们主要关注的是Kconfig在配置过程中所扮演的角色以及对输出的结果的影响。
 
Linux Kernel的配置过程也是在make的驱动下开始的,我们以"make menuconfig"的执行过程为例。
 
1、首先,顶层Makefile通过分析$(MAKECMDGOALS)判定"menuconfig"为config-targets,而非build-target或mixed-target(诸如make defconfig all这样的命令);
 
2、"menuconfig"与Makefile中预置target进行匹配
menuconfig会匹配到Makefile中的"config %config",下面是有关这个target的源码节选:
 
include $(srctree)/scripts/Kbuild.include
… …
include $(srctree)/arch/$(SRCARCH)/Makefile
export KBUILD_DEFCONFIG KBUILD_KCONFIG
 
config %config: scripts_basic outputmakefile FORCE
    $(Q)mkdir -p include/linux include/config
    $(Q)$(MAKE) $(build)=scripts/kconfig $@
 
"config %config"有三个依赖项:scripts_basic、outputmakefile和FORCE。其中outputmakefile、FORCE是两个空目标,而有关scripts_basic target的源码如下:
 
# Basic helpers built in scripts/
PHONY += scripts_basic
scripts_basic:
    $(Q)$(MAKE) $(build)=scripts/basic
 
scripts_basic target的构建实际上就是编译scripts/basic下的源文件。build变量在scritps/Kbuild.include中定义,其值为"-f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj",所以上述命令展开后就是"make -f scripts/Makefile.build obj=scripts/basic"。scripts/Makefile.build这个文件在整个Linux Kernel编译过程中占据极其重要的位置,其定义了核心编译的主要target(如.o、.s等)的构建命令规则。
 
3、menuconfig目标的构建
在依赖项scripts_basic构建完毕后,Make驱动执行menuconfig目标的构建:
首先,创建两个目录include/linux和include/config,然后执行"make -f scripts/Makefile.build obj=scripts/kconfig menuconfig"。target依旧是"menuconfig",但scripts/Makefile.build中似乎并没有这个target可供匹配啊。别急,我们看看scripts/Makefile.build中的这段代码:
 
src := $(obj)
… …
# The filename Kbuild has precedence over Makefile
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)
 
这是一段关键代码,kbuild-dir的求值结果为scripts/kconfig,由于scripts/kconfig下没有Kbuild文件,进而kbuild-file求值结果为scripts/kconfig/Makefile,并且该文件被Makefile.build包含了进来。从kbuild-file这个变量的命名可以看出为何各个子目录下的Makefile被称为Kbuild Makefile了。
 
scripts/kconfig/Makefile中包含了许多*config targets,其中就有"menuconfig"这个target:
 
ifdef KBUILD_KCONFIG
Kconfig := $(KBUILD_KCONFIG)
else
Kconfig := arch/$(SRCARCH)/Kconfig
endif
… …
menuconfig: $(obj)/mconf
    $< $(Kconfig)
 
该目标依赖scripts/kconfig/mconf,这是一个小工具程序,Make会首先执行该程序的编译链接;然后执行"scripts/kconfig/mconf arch/x86/Kconfig"($<是一个自动变量,指代该target的第一个依赖项,这里是scripts/kconfig/mconf)。
 
4、"scripts/kconfig/mconf arch/x86/Kconfig"的执行过程
到这里,Kconfig配置文件终于登场了!scripts/kconfig/mconf读取arch/x86/Kconfig,后者是一个针对x86这一体系的顶层Kconfig文件,打开arch/x86/Kconfig后,你会发现它内部source了许多其他Kconfig文件,诸如:
 
….
source "init/Kconfig"
source "kernel/time/Kconfig"
source "mm/Kconfig"
 
mconf会依次读入这些子Kconfig文件,并将配置项的符号表建立起来。如果你是第一次进行Linux Kernel配置,尚没有.config文件,那么这些配置项的初始值从哪里得到呢(不是所有配置项都有默认值)?在init/Kconfig文件中,你会看到这样的配置项:
 
config DEFCONFIG_LIST
    string
    depends on !UML
    option defconfig_list
    default "/lib/modules/$UNAME_RELEASE/.config"
    default "/etc/kernel-config"
    default "/boot/config-$UNAME_RELEASE"
    default "$ARCH_DEFCONFIG"
    default "arch/$ARCH/defconfig"
 
mconf应该就是通过这个DEFCONFIG_LIST配置项找到一份默认config文件的,mconf自上而下依次尝试,直到对应的文件存在,就将存在的文件作为默认.config加载,为各个配置项赋值。在我的RHEL 5.5上$ARCH-DEFCONFIG被作为默认.config加载了;而在我的Ubuntu 10.04上,mconf找到的是/boot/config-2.6.32-30-generic。下面是在RHEL 5.5上执行make menuconfig后控制台的输出结果:
 
$ make menuconfig
scripts/kconfig/mconf arch/x86/Kconfig
#
# using defaults found in arch/x86/configs/x86_64_defconfig
#
#
# configuration written to .config
#
 
*** End of Linux kernel configuration.
 
5、生成.config文件
手工完成配置后,如果选择了save配置,那么在源码顶层目录下会生成一个.config文件,这个就是整个Kernel配置过程的最重要的输出,这类似用户层应用在configure之后生成的Makefile,.config文件实际上也是一个Makefile文件,只是其内容格式比较单一罢了(都是CONFIG_XXX=y形式的变量定义)。
 
至此,Linux Kernel的配置过程Hack结束了,其他*config target执行过程也是大同小异的,再总结一下make menuconfig配置的执行过程:
 
* 首先建立include/config目录,并编译scripts/basic下的一些工具;
* 然后编译scripts/kconfig下的工具,比如mconf等
* 执行scripts/kconfig/mconf arch/x86/Kconfig,该程序会生成.config
 
二、make all阶段
Make *config后,Kconfig文件的使命就算是结束了。剩下的Makefile和Kbuild文件将会在make all阶段扮演重要角色。前面说过Kbuild本身就是Makefile,分布在各个子目录中Makefile也被称为Kbuild Makefile,其实还是Makefile,总而言之,Make all阶段其实就是关于Makefile的事情了。只不过Linux Kernel的整个Makefile组织体系设计的很精巧,特别是与配置阶段输出的结果配合的天衣无缝,下面我们就来具体看看吧。
 
在顶层的Makefile中,我们可以直接定位到Make all的target,不过有两个,依次是:
 
all: vmlinux
all: modules
 
Make会自动合并all的依赖项,并依次对依赖项进行构建,就类似这样:
 
all: foo1
all: foo2
 
foo1:
        @echo "foo1"
foo2:
        @echo "foo2"
 
$> make
foo1
foo2
 
而vmlinux这个target是什么情况呢?见下面代码摘要:
 
# vmlinux image – including updated kernel symbols
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
… …
    $(call vmlinux-modpost)
    $(call if_changed_rule,vmlinux__)
    $(Q)rm -f .old_version
 
… … 
# The actual objects are generated when descending, 
# make sure no implicit rule kicks in
$(sort $(vmlinux-init) $(vmlinux-main)) $(vmlinux-lds): $(vmlinux-dirs) ;
 
从代码中可以看到vmlinux有若干个依赖项,我们沿着这些依赖项继续"深度"搜索,你会发现这是一个"不算浅"的依赖项树型结构,树的根节点就是vmlinux。由于这棵树太过"枝繁叶茂",所以这里只想针对重要且关键的依赖项进行分析。
 
1、prepare
这个phony target是$(vmlinux-dirs)的依赖项,顾名思义,在真正地编译之前做些准备工作。目前所有的准备工作被划分到从prepare0到prepare3的多个phony targets中了,形成一个链式依赖关系,这么做也便于日后扩展:再增加一个prepare-n非常容易。
 
在prepare的过程中有若干的重要的文件被生成了:
 
include/linux/version.h: $(srctree)/Makefile FORCE
    $(call filechk,version.h)
 
include/linux/utsrelease.h: include/config/kernel.release FORCE
    $(call filechk,utsrelease.h)
 
include/config/kernel.release: include/config/auto.conf FORCE
    $(Q)rm -f $@
    $(Q)echo $(kernelrelease) > $@
 
include/config/auto.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd
    $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig
 
其中include/config/auto.conf依赖$(KCONFIG_CONFIG)和include/config/auto.conf.cmd,顶层Makefile中设置了auto.conf.cmd这个target的空规则:
 
# To avoid any implicit rule to kick in, define an empty command
$(KCONFIG_CONFIG) include/config/auto.conf.cmd: ;
 
这样实际上include/config/auto.conf在auto.conf.cmd尚未被创建的情况下只是依赖配置阶段输出的.config文件(KCONFIG_CONFIG  ?= .config)。而Kernel配置后,auto.conf也未被创建,因此在这里Make执行创建auto.conf的命令:make -f Makefile silentoldconfig,该命令的执行结果是auto.conf、auto.conf.cmd、include/linux/autoconf.h以及include/config下的诸多空头文件被创建了出来。
 
2、$(vmlinux-dirs)
$(vmlinux-dirs)是一个非常重要的target,$(vmlinux-init)、$(vmlinux-main)、$(vmlinux-lds)都依赖$(vmlinux-dirs)。
 
init-y      := init/
drivers-y   := drivers/ sound/ firmware/
net-y       := net/
libs-y      := lib/
core-y      := usr/
… …
 
core-y      += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
 
vmlinux-dirs    := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
             $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
             $(net-y) $(net-m) $(libs-y) $(libs-m)))
 
PHONY += $(vmlinux-dirs)
$(vmlinux-dirs): prepare scripts
    $(Q)$(MAKE) $(build)=$@
 
此时vmlinux-dirs的值是一组目录集合,诸如init usr kernel mm fs ipc security crypto block drivers sound firmware net lib等。Makefile将这些目录名视为phony target。这样$(vmlinux-dirs): prepare scripts这个规则实际上就是一个multiple targets规则,会被多次执行的,即会对每个target执行一次"$(Q)$(MAKE) $(build)=$@"。我们以init这个phony target为例展开命令:make -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build
obj=init,该命令将根据Makefile.build中定义的规则对init目录进行编译。Makefile.build规则中的默认phony target为__build:
 
PHONY := __build
 
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
     $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
     $(subdir-ym) $(always)
    @:
 
__build"复杂"的依赖项会将必要的目标包含进来,其中较关键的是builtin-target这个目标:
 
ifneq ($(strip $(obj-y) $(obj-m) $(obj-n) $(obj-) $(lib-target)),)
builtin-target := $(obj)/built-in.o
endif
 
#
# Rule to compile a set of .o files into one .o file
#
ifdef builtin-target
quiet_cmd_link_o_target = LD      $@
# If the list of objects to link is empty, just create an empty built-in.o
cmd_link_o_target = $(if $(strip $(obj-y)),\
              $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \
              $(cmd_secanalysis),\
              rm -f $@; $(AR) rcs $@)
 
$(builtin-target): $(obj-y) FORCE
    $(call if_changed,link_o_target)
 
targets += $(builtin-target)
endif # builtin-target
 
这样每个子目录(诸如mm、init等)下的主要目标就是built-in.o,其依赖的是$(obj-y),展开后其实是一个.o文件列表。
 
这里还要提一点,那就是配置阶段的输出是如何与Build阶段结合的,我们还是看一下init/Makefile,这里节选一些代码:
 
obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o
mounts-$(CONFIG_BLK_DEV_RAM)    += do_mounts_rd.o
mounts-$(CONFIG_BLK_DEV_INITRD) += do_mounts_initrd.o
mounts-$(CONFIG_BLK_DEV_MD) += do_mounts_md.o
 
可以看到.config中的配置项的值将各个.o文件分为几个类别,如果配置项值为y,则对应的.o文件归为obj-y列表;如果为m,则对应的.o文件归为obj-m列表,诸如此类(包括lib-y、lib-m、subdir-y、subdir-m等)。这样我们就可以通过调整配置项的值来选择是否将某功能编译到Linux Kernel中,还是以Kernel module形式存在,让人叹为观止!
 
3、$(vmlinux-lds)
在顶层Makefile源码中vmlinux-lds  := arch/$(SRCARCH)/kernel/vmlinux.lds,vmlinux.lds是vmlinux的linker script。vmlinux.lds也是在构建$(vmlinux-dirs)目标时被构建出来的。
 
$(vmlinux-dirs)中包含arch/x86,而在arch/x86/Makefile中,我们发现了这行代码:
 
core-y += arch/x86/kernel/
 
这样arch/x86/kernel被纳入编译,而vmlinux.lds就是arch/x86/kernel/Makefile中变量extra-y的一个值:
 
extra-y                := head_$(BITS).o head$(BITS).o head.o init_task.o vmlinux.lds
 
vmlinux.lds会被构建,而.lds目标构建规则在scripts/Makefile.build中:
 
# Linker scripts preprocessor (.lds.S -> .lds)
# —————————————————————————
quiet_cmd_cpp_lds_S = LDS     $@
      cmd_cpp_lds_S = $(CPP) $(cpp_flags) -D__ASSEMBLY__ -o $@ $<
 
$(obj)/%.lds: $(src)/%.lds.S FORCE
    $(call if_changed_dep,cpp_lds_S)
 
OK,vmlinux.lds的来龙去脉也算是搞清楚了。
 
4、其他
vmlinux的依赖还包括vmlinux-init、vmlinux-main以及vmlinux.o,见下面顶层Makefile的节选代码:
 
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
 
modpost-init := $(filter-out init/built-in.o, $(vmlinux-init))
vmlinux.o: $(modpost-init) $(vmlinux-main) FORCE
    $(call if_changed_rule,vmlinux-modpost)
 
有了vmlinux-dirs那节的说明,这些依赖项的构建也是大同小异的。
 
当vmlinux的所有依赖项都构建完毕后,vmlinux的创建也就水到渠成了,这里就不多说了。
 
总而言之,Linux Kernel简单配置和编译的背后其实还是蛮复杂的,如果要挖掘细节,即使有了上面的Hack,也还是会耗费你一定时间的^_^。

 

也谈Linux Kernel Hacking – 内核配置、编译与安装

Linux Kernel之于C程序员,就好比世界之巅珠穆朗玛之于专业登山客。 — Tony Bai^_^

 
作为到目前为止最为成功的开源项目,Linux Kernel总是散发着无穷的魅力,就好比那珠穆朗玛,让人魂牵梦绕,心潮澎湃并总是想尝试征服。
 
记得2006年初我曾花了些时间研究Linux Kernel,但后来迷失在了Linux Kernel引导阶段,无法自拔,最终选择了"知难而退"。如今,随着我们的产品越来越多地运行在Linux主机上,我又愈发感觉自己对Linux底层了解得不够深入,于是我又一次开启了Linux Kernel Hacking的征程。
 
经过这几年的发展,Linux Kernel变得更加复杂了,版本也从当时的2.6.x演进到今天的3.2.x(3.3正在开发中)。但相应地,Linux Kernel方面的资料也多了许多,这对我的Hacking显然是利好消息,至少目前手头上就有几本"大砖头"可作为参考(Linux Kernel方面的书籍均具有防身之特性^_^)。
 
这次Hacking前先给自己设定了几个目标(也算是想清楚为何要这么做):
* 追溯本源
用Linux内核运转原理解释上层应用的行为并指导上层应用的开发。
 
* 定制优化
在对Linux Kernel有了深入了解之后,尝试定制适合产品特性的Linux内核。
 
* 走进内核开发,尝试提交补丁
对我个人来说,这算是在Linux Kernel领域的终极目标了。每天重复念叨这个目标,就相当于给自己打鸡血了,让自己始终保持兴奋劲儿。
 
以上这些目标显然不是短期内能达成的,饭还得一口一口吃,路还得一步一步走。今天我就迈出这第一步:编译一个属于自己的Linux内核。
 
经过这么多年的发展,Linux Kernel的编译已经简化了许多了,甚至简化到了让我觉得有些吃惊的地步(在我原先的意识中,Linux Kernel的编译是件很复杂的事情^_^)。
 
编译内核是Linux Kernel开发者的基本活动,几乎所有Kernel开发者都是在自己编译的内核上工作的。下面我就详细说明一下Linux Kernel的配置、编译和安装过程。
 
一、准备工作
1、准备一台装有Linux的PC
不建议在Windows或Solaris下编译Linux Kernel,那样只会自找麻烦。Linux Kernel在Linux下编译才是正路(除非你真的要做跨平台交叉编译)。我这里用了一台运行在XenServer 5.6 p2上的装有Red Hat Enterprise Linux(RHEL) 5.5的虚拟机。在该虚拟机上执行'uname -r',可以得到当前Linux内核版本信息为:2.6.18-194.el5xen。
 
2、获取内核源码包
Linux Kernel,特别是之前发布的稳定版内核,几乎都可以100%的顺利通过编译。为了能与手头资料"兼容",我选择了2.6.28版本内核。Linux Kernel的发布版本可在http://www.kernel.org/pub/linux/kernel下找到,这里执行下面命令获取源码:
 
wget -c http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.28.tar.gz
 
下载后的源码包无需放在系统目录(/usr/src/linux)下,在你自己的普通权限用户下建立一个临时目录存放源码包即可,比如我们在/home/tonybai下建立linux-kernel目录,将下载的linux-2.6.28.tar.gz放入该目录中,解压后(tar xvzf linux-2.6.28.tar.gz),我们会看到:
 
/home/tonybai/linux-kernel$ ls 
linux-2.6.28/  linux-2.6.28.tar.gz
 
3、检查编译内核所依赖的工具及版本是否满足要求
在linux-2.6.28/Documentation/Changes中有该版本内核编译所依赖的工具以及最低版本信息列表,需确认一下当前主机上是否安装了这些工具,版本是否满足最低要求。通过linux-2.6.28/scripts/ver_linux可以快速获取当前主机上各个工具以及当前版本的信息,可将这些信息与编译该内核的最低版本比对,以确定是否需要安装或升级工具版本。
 
二、配置内核
Linux Kernel的编译有些类似于那些使用autotools创建构建脚本的开源包,需要先Configure,然后make和make install。不同的是Linux Kernel的"Configure"要稍显"复杂",毕竟与普通开源包相比,Linux Kernel算得上是一个庞然大物了。不过Linux Kernel的开发者们显然在这方面也做了很多工作,通过提供各种命令和默认配置来简化配置过程,下面是常用的几个配置命令。
 
* make config
这个是最基本的配置命令,同时也是配置过程最复杂、耗时最长的配置命令。该命令会将Linux Kernel所有配置项逐一在控制台窗口输出,并让你作出yes、no或是module的选择。我查看了一下RHEL 5.5的配置项个数,总共有2300多项,想必这个过程下来,你已经筋疲力尽了。所以除了某些特殊情况,我们是不会使用这个命令的。该命令会在linux-2.6.28目录下面创建一个.config隐藏文件,该文件存储了你的配置选择,类似这样:
 
# .config
#
# Automatically generated make config: don't edit
# Linux kernel version: 2.6.28
# Wed Mar 14 17:13:23 2012
#
# CONFIG_64BIT is not set
CONFIG_X86_32=y
# CONFIG_X86_64 is not set
CONFIG_X86=y
CONFIG_ARCH_DEFCONFIG="arch/x86/configs/i386_defconfig"
CONFIG_GENERIC_TIME=y
… …
 
* make defconfig 
一个一个选择配置太累,内核开发者显然也不原意这样做,因此内核提供了另外一个命令make defconfig。这个命令会为你生成一份默认的.config文件,而整个过横无需你作出任何选择。而实际上该命令是直接将arch/x86/configs/i386_defconfig或x86_64_defconfig(以x86平台为例)拷贝为.config放在linux-2.6.28下面。
 
* make menuconfig
虽然有了默认配置,但开发者总是有修改配置的需求。内核提供了make menuconfig命令,允许开发者以图形界面(基于ncurses)的形式修改特定的配置项。根据大家的喜好不同,内核还提供了基于gtk+图形界面的make gconfig和基于X11图形界面的make xconfig来修改配置项,这两个命令在功用上与make menuconfig是等同的。
 
另外还有一种方法配置内核,那就是直接使用Linux发行版自带的.config或其他开发者的.config来配置你的内核。如果你是第一次配置内核,建议直接使用所在主机的Linux的.config。我所用的Linux的.config文件在/usr/src/kernels/2.6.18-194.el5-xen-x86_64下面。不过由于我下载的Kernel版本是2.6.28,与该.config不匹配,所以还需执行'make
oldconfig'命令来更新配置。该命令会保留.config已有的配置项的值,而对于新Kernel版本引入的新配置项提供交互式的选择。我用的就是这种方法:
 
$ make oldconfig
scripts/kconfig/conf -o arch/x86/Kconfig
#
# configuration written to .config
#
 
三、编译内核
配置好内核后,我们就可以执行内核编译了,和上层应用一样,只需一个Make就好。
 
$ make
… ..
  CC      arch/x86/boot/tty.o
  CC      arch/x86/boot/video.o
  CC      arch/x86/boot/video-mode.o
  CC      arch/x86/boot/version.o
  CC      arch/x86/boot/video-vga.o
  CC      arch/x86/boot/video-vesa.o
  CC      arch/x86/boot/video-bios.o
  LD      arch/x86/boot/setup.elf
  OBJCOPY arch/x86/boot/setup.bin
  OBJCOPY arch/x86/boot/vmlinux.bin
  HOSTCC  arch/x86/boot/tools/build
  BUILD   arch/x86/boot/bzImage
Root device is (8, 1)
Setup is 10988 bytes (padded to 11264 bytes).
System is 3561 kB
CRC f4d6ad54
Kernel: arch/x86/boot/bzImage is ready  (#1)
  Building modules, stage 2.
  MODPOST 3 modules
  CC      arch/x86/kernel/test_nx.mod.o
  LD [M]  arch/x86/kernel/test_nx.ko
  CC      drivers/hid/hid-dummy.mod.o
  LD [M]  drivers/hid/hid-dummy.ko
  CC      drivers/scsi/scsi_wait_scan.mod.o
  LD [M]  drivers/scsi/scsi_wait_scan.ko
 
整个编译过程(非跨平台交叉编译,只是本地编译)大约20多分钟,编译成功后,我们得到了许多新文件,其中重要的文件有:
 
linux-2.6.28/vmlinux
linux-2.6.28/System.map
linux-2.6.28/arch/x86/boot/bzImage
 
其中bzImage就是我们编译好的可引导的、压缩的Linux内核映像文件。而System.map则是内核符号表文件,vmlinux是未经压缩的内核文件。
 
四、安装内核
安装内核与配置、编译内核不同,它需要root权限。切换到root后,我们首先需要安装的是内核模块,内核模块将会被安装到/lib/modules下面:
 
$make modules_install
… …
$ls -l /lib/modules/
总计 8
drwxr-xr-x 6 root root 4096 11-17 15:14 2.6.18-194.el5xen/
drwxr-xr-x 3 root root 4096 03-14 08:58 2.6.28
 
接下来就可以安装内核了。不过在安装之前,我们先看看当前系统内核文件是什么样子、Grub的配置又是怎样的:
 
$ ls -l /boot
-rw-r–r– 1 root root   66548 2010-03-17 config-2.6.18-194.el5xen
-rw——- 1 root root 3397337 11-17 15:14 initrd-2.6.18-194.el5xen.img
-rw-r–r– 1 root root 1208685 2010-03-17 System.map-2.6.18-194.el5xen
-rw-r–r– 1 root root 2047518 2010-03-17 vmlinuz-2.6.18-194.el5xen
-rw-r–r– 1 root root  417317 2010-03-17 xen.gz-2.6.18-194.el5
-rwxr-xr-x 1 root root  969808 2010-03-17 xen-syms-2.6.18-194.el5
 
$ vi /boot/grub/grub.conf
#boot=/dev/hda
default=0
timeout=5
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title Red Hat Enterprise Linux Server (2.6.18-194.el5xen)
        root (hd0,0)
        kernel /xen.gz-2.6.18-194.el5 crashkernel=128M@32M
        module /vmlinuz-2.6.18-194.el5xen ro root=/dev/VolGroup00/LogVol00 rhgb quiet
        module /initrd-2.6.18-194.el5xen.img
 
可以看出vmlinuz-*这个文件就是内核映像文件,它其实就是arch/x86/boot/bzImage的拷贝;但我们无法通过直接压缩vmlinux来得到vmlinuz-*,据说vmlinuz在头部放置了gzip的解压代码。
 
我们通过make install进行内核安装:
$ make install
sh /home/tonybai/linux-kernel/linux-2.6.28/arch/x86/boot/install.sh 2.6.28 arch/x86/boot/bzImage System.map "/boot"
 
make install调用的是对应arch下提供的install.sh来安装内核。arch/x86/boot/install.sh检测系统中是否安装了installkernel脚本,如果有则调用installkernel工具安装内核,否则进行默认安装。至少在Red Hat的发行版上我们是可以找到installkernel这个脚本的。installkernel除了将bzImage和System.map安装到/boot下之外,还调用了/sbin/new-kernel-pkg制作了initrd-2.6.28.img,并修改了grub.conf(使用grubby配置grub)的内容:
 
$ ls -l /boot
-rw——- 1 root root 3369458 03-14 08:59 initrd-2.6.28.img
lrwxrwxrwx 1 root root      33 03-14 10:41 System.map -> /boot/System.map-2.6.28
-rw-r–r– 1 root root 1397880 03-14 08:58 System.map-2.6.28
lrwxrwxrwx 1 root root      30 03-14 10:41 vmlinuz -> /boot/vmlinuz-2.6.28
-rw-r–r– 1 root root 2080528 03-14 08:58 vmlinuz-2.6.28
 
$ vi /boot/grub/grub.conf
default=1
timeout=5
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title Red Hat Enterprise Linux Server (2.6.28)
        root (hd0,0)
        kernel /vmlinuz-2.6.28 ro root=/dev/VolGroup00/LogVol00 rhgb quiet
        initrd /initrd-2.6.28.img
title Red Hat Enterprise Linux Server (2.6.18-194.el5xen)
        root (hd0,0)
        kernel /xen.gz-2.6.18-194.el5 crashkernel=128M@32M
        module /vmlinuz-2.6.18-194.el5xen ro root=/dev/VolGroup00/LogVol00 rhgb quiet
        module /initrd-2.6.18-194.el5xen.img
 
make install虽然对grub.conf进行了修改,但默认引导的内核依旧是原先的内核,我们需要手工将default改为0来引导我们新编译的2.6.28内核。
 
五、引导新内核
安装了新内核后,我们尝试使用新内核引导启动。执行Reboot后,新内核引导一切顺利。用'uname -r'查看结果如下:
 
$ uname -r
2.6.28
 
至此,我们成功用上了自己编译的内核(后续应该会有关于内核引导阶段的详细Hacking描述^_^)。
 
六、升级内核
升级内核是内核开发者的日常活动之一。当有其他开发者发布新补丁或自己在现有内核上做了修改后,都会重新配置、编译和安装内核,也就是升级内核。
 
升级内核一般按如下如下命令序列执行:
$ make oldconfig
$ make
$ make install(如果有kernel module更新,应该先执行make modules_install)
 
对于版本号不变的内核重新执行install,我们会在/boot下看到如下内容:
$ ls -l /boot
-rw——- 1 root root 3369458 03-14 08:59 initrd-2.6.28.img
lrwxrwxrwx 1 root root      33 03-14 10:41 System.map -> /boot/System.map-2.6.28
-rw-r–r– 1 root root 1397880 03-14 08:58 System.map-2.6.28
-rw-r–r– 1 root root 1397880 03-14 08:51 System.map-2.6.28.old
lrwxrwxrwx 1 root root      30 03-14 10:41 vmlinuz -> /boot/vmlinuz-2.6.28
-rw-r–r– 1 root root 2080528 03-14 08:58 vmlinuz-2.6.28
-rw-r–r– 1 root root 2080528 03-14 08:51 vmlinuz-2.6.28.old
 
安装脚本会将上一次安装的2.6.28内核改名为2.6.28.old,然后将新内核安装到/boot下。grub.conf内容没有被修改。再次反复执行install,安装脚本始终会将老内核改名为.old,然后保证最新同版本内核被安装。
 
七、定义自己的个性化内核版本号
XenServer下的Rhel 5.5的内核版本号为2.6.18-194.el5xen,Ubuntu 10.04下的内核版本号为2.6.32-30-generic(通过uname -r查看),我们如何定义一个属于自己的个性化内核版本号呢?其实很简单,修改顶层Makefile即可。
 
# Makefile 
 
VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 28
EXTRAVERSION = -tonybai-dev
 
一个Kernel的版本号KERNELVERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION),因此我们可通过修改EXTRAVERSION的内容来定义一个个性化的版本号,就像上面代码中的那样。
 
修改Makefile后,执行make clean; make ;make modules_install; make install即可。执行后,你就会在/boot下面、/lib/modules下面以及grub.conf里面看到2.6.28-tonybai-dev这个版本的内核信息了。修改grub.conf使之默认引导2.6.28-tonybai-dev这个新内核,重新引导后,你执行'uname -r'的结果就会变成'2.6.28-tonybai-dev'了。
 
至此,内核配置、编译与安装的部分就暂时告一段落了。在这个过程中,我参考了许多资料,这其中包括:
 
相信后续的Hacking过程中,这些资料还将会发挥至关重要的作用。
如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats