第 7 章 配置编译内核
7.2 配置编译内核源码
7.2.4 内核编译
1.编译命令
Makefile 还提供了配置编译的选项或者规则。执行 make help,可以打印出详细的帮助信 息。解释一下帮助信息列出的各种选项的含义,分别在每一行信息下面加以注释。
$make help
打印出下列帮助信息。
(1)用于清理生成文件的目标(Cleaning targets)
clean - remove most generated files but keep the config clean 目标可以清除大多数生成的文件,但是保留.config。
mrproper - remove all generated files + config + various backup files mrproper 可以清除所有生成的文件,包括.config 和各种备份文件。
(2)内核配置的目标(Configuration targets)
config - Update current config utilising a line-oriented program config 是命令行的内核配置方式。
menuconfig - Update current config utilising a menu based program
tyw藏书
menuconfig 是光标菜单内核配置方式。
xconfig - Update current config utilising a QT based front-end xconfig 是基于 QT 图形界面的内核配置方式。
gconfig - Update current config utilising a GTK based front-end gconfig 是基于 GTK 图形界面的内核配置方式
oldconfig - Update current config utilising a provided .config as base oldconfig 基于已有的.config 文件进行内核配置。
randconfig - New config with random answer to all options randconfig 是对所有的选项按照随机回答(Y/M/N)的方式生成新配置。
defconfig - New config with default answer to all options defconfig 是对所有的选项都按照缺省回答生成新配置。
allmodconfig - New config selecting modules when possible allmodconfig 是对所有选项尽可能配置模块的新配置。
allyesconfig - New config where all options are accepted with yes allyesconfig 是对所有选项都配置成“Yes”的最大配置。
allnoconfig - New minimal config
allnoconfig 是对所有选项都配置成“No”的最小配置。
(3)其他通用目标(Other generic targets)
all - Build all targets marked with [*]
all 是编译所有标记星号的目标,也就是编译所有缺省目标。
* vmlinux - Build the bare kernel
vmlinux 是编译最基本的内核映像,就是顶层的 vmlinux。
* modules - Build all modules modules 是编译所有的模块。
modules_install - Install all modules modules_install 是安装所有的模块。
tyw藏书
dir/ - Build all files in dir and below
dir 是编译 dir 目录及其子目录的所有文件,当然 dir 代表具体的一个目录名。
dir/file.[ois] - Build specified target only dir/file.[ois]是仅编译 dir 目录下指定的目标。
dir/file.ko - Build module including final link dir/file.ko 是编译并且链接指定目录的模块。
rpm - Build a kernel as an RPM package rpm 是以 RPM 包方式编译内核。
tags/TAGS - Generate tags file for editors tags/TAGS 是为编辑器生成 tag 文件,方便编辑器识别关键词。
cscope - Generate cscope index cscope 是生成 cscope 索引,方便代码浏览。
kernelrelease - Output the release version string kernelrelease 是输出内核版本的字符串。
(4)静态解析器(Static analysers)
buildcheck - List dangling references to vmlinux discarded sections and init sections from non-init sections
buildcheck 是列出对 vmlinux 废弃段的虚引用和从非 init 段引用 init 段的虚引用。
checkstack - Generate a list of stack hogs checkstack 是生成栈空间耗费者的列表。
namespacecheck - Name space analysis on compiled kernel namespace 是对编译好的内核做命名域分析。
(5)内核打包(Kernel packaging)
rpm-pkg - Build the kernel as an RPM package rpm-pkg 是以一个 RPM 包的方式编译内核。
binrpm-pkg - Build an rpm package containing the compiled kernel and modules
tyw藏书
binrpm-pkg 是编译一个包含已经编译好的内核和模块的 rpm 包。
deb-pkg - Build the kernel as an deb package deb-pkg 是以一个 deb 包的方式编译内核。
tar-pkg - Build the kernel as an uncompressed tarball tar-pkg 是以一个不压缩的 tar 包方式编译内核。
targz-pkg - Build the kernel as a gzip compressed tarball targz-pkg 是以一个 gzip 压缩包的方式编译内核。
tarbz2-pkg - Build the kernel as a bzip2 compressed tarball tarbz2-pkg 是以一个 bzip2 压缩包的方式编译内核。
(6)文档目标(Documentation targets)
Linux kernel internal documentation in different formats:
xmldocs (XML DocBook), psdocs (Postscript), pdfdocs (PDF)
htmldocs (HTML), mandocs (man pages, use installmandocs to install) Linux 内核内部支持各种形式的文档。
(7)体系结构相关的目标(ARM)(Architecture specific targets (arm))
* zImage - Compressed kernel image (arch/arm/boot/zImage) zImage 是编译生成压缩的内核映像(arch/arm/boot/zImage)。
Image - Uncompressed kernel image (arch/arm/boot/Image) Image 是编译生成非压缩的内核映像(arch/arm/boot/Image)。
* xipImage - XIP kernel image, if configured (arch/arm/boot/xipImage) xipImage 是编译生成 XIP 的内核映像(arch/arm/boot/xipImage),前提是内核配置成 XIP。
bootpImage - Combined zImage and initial RAM disk
(supply initrd image via make variable INITRD=<path>) bootpImage 是编译包含 zImage 和 initrd 的映像(可以通过 make 变量 INITRD=<path>提 供 initrd 映像)。
install - Install uncompressed kernel install 是安装非压缩的内核。
zinstall - Install compressed kernel
tyw藏书
Install using (your) ~/bin/installkernel or (distribution) /sbin/installkernel or install to $(INSTALL_PATH) and run lilo
zinstall 是 安 装 压缩 的 内核。 通过发 行版的 /bin/installkernel 工 具 安 装 , 或者安 装 到
$(INSTALL_PATH)路径下,然后再执行 lilo。这些目标对于 X86 平台适用。
还有各种开发板的缺省内核配置文件,这些配置文件都保存在 arch/arm/configs 目录下。
assabet_defconfig - Build for assabet
……
smdk2410_defconfig - Build for smdk2410 spitz_defconfig - Build for spitz versatile_defconfig - Build for versatile 每种支持的目标板都会保存一个缺省的内核配置文件。
make V=0|1 [targets] 0 => quiet build (default), 1 => verbose build V=0 表示不显示编译信息(缺省),V=1 表示显示编译信息。
make O=dir [targets] Locate all output files in "dir", including .config O=dir 用来指定所有输出文件的目录,包括.config 文件,都将放到 dir 目录下。
make C=1 [targets] Check all c source with $CHECK (sparse) C=1 表示检查所有$CHECK 的 C 程序。
make C=2 [targets] Force check of all c source with $CHECK (sparse) C=2 表示强制检查所有$CHECK 的 C 程序。
Execute "make" or "make all" to build all targets marked with [*]
执行 make 或者 make all,将自动编译所有带星号标志的目标。
For further info see the ./README file 更多信息参看 REAME 文件。
其中,vmlinux modules zImage 和 xipImage 是 Makefile 缺省的目标。执行 make,缺省地 就可以执行这些编译规则。但是,zImage 和 xipImage 是互斥的,因为两种内核映像格式不可 能同时配置。
2.编译链接内核映像
一般情况下,先编译链接生成顶层目录的 vmlinux,再把 vmlinux 精简压缩成 piggy.gz,
然后加上自引导程序链接成 arch/$(ARCH)/boot/zImage,这样就得到一个具备自启动能力的
tyw藏书
Linux 内核映像。
除了 zImage 之外,还有其他一些映像格式,分别适用于不同的体系结构和引导程序。
由于 zImage 是最通用的,所以我们只分析一下 zImage 编译链接的过程。
(1)编译链接 vmlinux
vmlinux 的规则是在顶层的 Makefile 中定义的。vmlinux 是由$(vmlinux-init)和$(vmlinux-main) 列表中指定的目标文件链接而成的,大多数是来自顶层子目录下的 built-in.o 文件,其他都在 arch/$(ARCH)Makefile 中指定。这些目标文件的链接顺序非常重要,$(vmlinux-init)必须排在 第一位。参考 Makefile 注释中的结构图。
vmlinux 的版本(uname-v 可以显示)不是在各级目录的编译阶段更新的,因为还不知道 是否需要更新 vmlinux。除了在添加内核符号之前生成 kallsysms 信息的情况,直到链接 vmlinux 才更新 vmlinux 版本信息。还生成 System.map 文件,用来描述所有符号(全局变量、
函数等)的地址。
#Makefile
#
# vmlinux
# ^
# |
# +-< $(vmlinux-init)
# | +--< init/version.o + more
# |
# +--< $(vmlinux-main)
# | +--< driver/built-in.o mm/built-in.o + more
# |
# +-< kallsyms.o (see description in CONFIG_KALLSYMS section)
#
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y) vmlinux-all := $(vmlinux-init) $(vmlinux-main)
vmlinux-lds := arch/$(ARCH)/kernel/vmlinux.lds
# Rule to link vmlinux - also used during CONFIG_KALLSYMS
# May be overridden by arch/$(ARCH)/Makefile quiet_cmd_vmlinux__ ?= LD $@
cmd_vmlinux__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) -o $@ \ -T $(vmlinux-lds) $(vmlinux-init) \ --start-group $(vmlinux-main) --end-group \
$(filter-out $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) FORCE ,$^) 这里通过定制 Kbuild 命令来定义 vmlinux 的规则。cmd_vmlinux__命令就是具体链接生
tyw藏书
成 vmlinux 的 Kbuild 命令。命令各部分的具体含义如下。
$(LD)是链接工具,对于 ARM 平台,就是 arm-linux-ld;
$(LDFLAGS)和$(LDFLAGS_vmlinux)是链接选项列表;
“-o $@”选项指定了生成的文件,$@在编译 vmlinux 目标的时候,代表 vmlinux 文件;
“-T $(vmlinux-lds)”选项指定链接脚本,arch/$(ARCH)/kernel/vmlinux.lds 就是这个脚本,
vmlinux-lds 是在 Makefile 中定义的。
“$(vmlinux-init)”是初始化部分目标文件列表,放在开头。
“--start-group $(vmlinux-main) --end-group”是内核主要的目标文件,链接的时候要检查 函数等符号是否确定。
剩余的代码或者数据链接在最后。
(2)生成 vmlinux.lds 链接脚本
vmlinux 的 链 接 脚 本 是 arch/$(ARCH)/kernel/vmlinux.lds 。 Kbuild 可 以 根 据 模 板 vmlinux.lds.S 转换生成。在 scripts/Makefile.build 中定义了一条把.lds.S 转换成.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 $@ $<
%.lds: %.lds.S FORCE
$(call if_changed_dep,cpp_lds_S)
摘取 arch/arm/kernel/vmlinux.lds.S 的部分内容,解释一下。
/* arch/arm/kernel/vmlinux.lds.S */
/* 由于使用了一些宏,所以需要包含这几个宏定义的头文件 */
#include <asm-generic/vmlinux.lds.h>
#include <linux/config.h>
#include <asm/thread_info.h>
OUTPUT_ARCH(arm) /* 指定目标板体系结构 */
ENTRY(stext) /* 代码段入口 */
SECTIONS /* 代码段各部分 */
{
. = TEXTADDR; /* 代码段起始地址,大多数 Linux 内核是 0xC0008000 */
.init : { /* 内核初始化的代码和数据 */
_stext = .;
_sinittext = .;
*(.init.text) _einittext = .;
tyw藏书
__proc_info_begin = .;
*(.proc.info.init) __proc_info_end = .;
__arch_info_begin = .;
*(.arch.info.init) __arch_info_end = .;
……
}
/DISCARD/ : { /* 内核退出的代码和数据 */
*(.exit.text) *(.exit.data) *(.exitcall.exit) }
.text : { /* 真正的代码段部分 */
_text = .; /* 代码和只读数据 */
*(.text) SCHED_TEXT LOCK_TEXT *(.fixup) *(.gnu.warning) *(.rodata) *(.rodata.*) *(.glue_7) *(.glue_7t)
*(.got) /* Global offset table */
} RODATA
_etext = .; /* 代码段和只读数据结束 */
……
.data : AT(__data_loc) { /* 数据段起始 */
__data_start = .; /* 内存中的地址 */
……
/* 例外修正表(可能需要在运行时修正) */
. = ALIGN(32);
__start___ex_table = .;
*(__ex_table)
tyw藏书
__stop___ex_table = .;
/* 普通的数据段 */
*(.data) CONSTRUCTORS
_edata = .; /* 数据段结束 */
}
.bss : { /* 未初始化的全局变量 */
__bss_start = .; /* BSS */
*(.bss) *(COMMON) _end = .;
}
/* 调试信息和数据段.*/
.stab 0 : { *(.stab) } .stabstr 0 : { *(.stabstr) } .stab.excl 0 : { *(.stab.excl) } .stab.exclstr 0 : { *(.stab.exclstr) } .stab.index 0 : { *(.stab.index) } .stab.indexstr 0 : { *(.stab.indexstr) } .comment 0 : { *(.comment) }
}
vmlinux 程序的链接组装,就是完全按照上面脚本的顺序。这样,内核代码和数据才能 加载到相应的位置运行。
(3)链接生成 zImage
zImage 的规则是在 arch/$(ARCH)Makefile 中定义的,它总是与目标板体系结构有关。
#arch/arm/Makefile
zImage Image xipImage bootpImage uImage: vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
zImage 的前提条件是 vmlinux,也就是说,只有顶层的 vmlinux 编译通过,才能生成 zImage。
编译命令是到 arch/$(ARCH)/boot 目录下,调用 Makefile 的 zImage 规则,同时传递变量 MACHINE。其中$@就是 zImage,这个规则又在 arch/arm/boot/Makefile 中定义。
#arch/arm/boot/Makefile
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE
tyw藏书
$(call if_changed,objcopy) @echo ' Kernel: $@ is ready'
这条规则的前提条件是$(obj)/compressed/vmlinux,那么这又要编译子目录 compressed。
事 实 上 , 对 于 不 同 的 体 系 结 构 , 这 部 分 代 码 有 很 大 差 异 。 对 于 ARM 平 台 来 说 ,
$(obj)/compressed/vmlinux 就是压缩的自引导映像,只不过没有精简。
在 arch/arm/boot/compressed/Makefile 文件中,定义了编译链接$(obj)/compressed/vmlinux 的规则。
#arch/arm/boot/compressed/Makefile
$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o \ $(addprefix $(obj)/, $(OBJS)) FORCE
$(call if_changed,ld) @:
前提条件中,$(obj)/vmlinux.lds 是链接脚本,$(obj)/$(HEAD)是自引导的目标代码,
$(obj)/piggy.o 是顶层 vmlinux 的精简压缩代码。为了保证自引导代码组装在映像起始位置,
还要使用链接脚本。
3.编译内核模块
Linux 2.6 内核的模块采用新的加载器,它是由 Rusty Russel 开发的。它使用内核编译 机制,生成一个*.ko(内核目标文件,kernel object)模块目标文件,而不是一个*.o 模块目 标文件。
内核编译系统首先编译这些模块,然后链接上 vermagic.o。这样就在目标模块创建了一 个特殊区域,用来记录编译器版本号、内核版本号、是否使用内核抢占等信息。
新的内核编译系统如何来编译并加载一个简单的模块的呢?举例一个简单的例子说明。
我们写一个最简单的“hello”模块,只要实现模块初始化函数和退出函数就够了。这个模块 程序叫作 hello.c。
#drivers/char/hello/hello.c void init_module (void) {
printk( "Hello module!\n");
}
void cleanup_module (void);
{
printk( "Bye module!\n");
}
相应的 Makefile 文件如下。
KERNEL_SRC = ~/linux-2.6.14
tyw藏书
SUBDIR = $(KERNEL_SRC)/drivers/char/hello/
all: modules
obj-m := hello_mod.o hello-objs := hello.o EXTRA_FLAGS += -DDEBUG=1 modules:
$(MAKE) -C $(KERNEL_SRC) SUBDIR=$(SUBDIR) modules
makefile 文件使用内核编译机制来编译模块。编译好的模块将被命名为 hello_mod.ko,
它是编译 hello.c 并且链接 vermagic.o 而得到的。KERNEL_SRC 指定内核源文件所在的目录,
SUBDIR 指定放置模块的目录。EXTRA_FLAGS 指定了需要给出的编译标记。
新模块要用新的模块工具加载或卸载。原来 2.4 内核的工具不能再用来加载或缷载 2.6 内核模块。2.4 的内核模块可能会发生使用和卸载冲突的情况,这是由于模块使用计数是由模 块代码自己来控制的。新的模块加载工具可以尽量避免这种情况发生。
新模块要用新的模块工具加载或卸载。原来 2.4 内核的工具不能再用来加载或缷载 2.6 内核模块。2.4 的内核模块可能会发生使用和卸载冲突的情况,这是由于模块使用计数是由模 块代码自己来控制的。新的模块加载工具可以尽量避免这种情况发生。