• 沒有找到結果。

内核编译

在文檔中 目 录 (頁 190-200)

第 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 的内核模块可能会发生使用和卸载冲突的情况,这是由于模块使用计数是由模 块代码自己来控制的。新的模块加载工具可以尽量避免这种情况发生。

在文檔中 目 录 (頁 190-200)