在研读 Linux 代码之前,我们应该对 Linux 的组成、编译和链接方式有一些基本的了解。实现此目标的一个直接方法是理解 Linux 的 makefile 文件。如果您喜欢在线浏览源代码,请查看 Linux 交叉引用。
以下是此顶层 makefile 中的一些常用目标
xconfig, menuconfig, config, oldconfig: 生成内核配置文件linux/.config;
depend, dep: 生成依赖文件,例如linux/.depend, linux/.hdepend以及.depend在子目录中;
vmlinux: 生成常驻内核镜像linux/vmlinux,最重要的目标;
modules, modules_install: 生成模块并安装到/lib/modules/$(KERNELRELEASE);
tags: 生成标签文件linux/tags,用于使用 vim 浏览源代码。
概述linux/Makefile如下所示
include .depend
include .config
include arch/i386/Makefile
vmlinux: generate linux/vmlinux
/* entry point "stext" defined in arch/i386/kernel/head.S */
$(LD) -T $(TOPDIR)/arch/i386/vmlinux.lds -e stext
/* $(HEAD) */
+ from arch/i386/Makefile
arch/i386/kernel/head.o
arch/i386/kernel/init_task.o
init/main.o
init/version.o
init/do_mounts.o
--start-group
/* $(CORE_FILES) */
+ from arch/i386/Makefile
arch/i386/kernel/kernel.o
arch/i386/mm/mm.o
kernel/kernel.o
mm/mm.o
fs/fs.o
ipc/ipc.o
/* $(DRIVERS) */
drivers/...
char/char.o
block/block.o
misc/misc.o
net/net.o
media/media.o
cdrom/driver.o
and other static linked drivers
+ from arch/i386/Makefile
arch/i386/math-emu/math.o (ifdef CONFIG_MATH_EMULATION)
/* $(NETWORKS) */
net/network.o
/* $(LIBS) */
+ from arch/i386/Makefile
arch/i386/lib/lib.a
lib/lib.a
--end-group
-o vmlinux
$(NM) vmlinux | grep ... | sort > System.map
tags: generate linux/tags for vim
modules: generate modules
modules_install: install modules
clean mrproper distclean: clean up build directory
psdocs pdfdocs htmldocs mandocs: generate kernel documents
include Rules.make
rpm: generate an rpm |
Rules.make包含在多个 Makefile 之间共享的规则。
编译后,ld 组合多个目标文件和归档文件,重新定位它们的数据并解决符号引用。linux/arch/i386/vmlinux.lds被指定为linux/Makefile在链接常驻内核镜像时使用的链接器脚本linux/vmlinux.
/* ld script to make i386 Linux kernel
* Written by Martin Mares <mj@atrey.karlin.mff.cuni.cz>;
*/
OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)
/* "ENTRY" is overridden by command line option "-e stext" in linux/Makefile */
ENTRY(_start)
/* Output file (linux/vmlinux) layout.
* Refer to Using LD, the GNU linker: Specifying Output Sections */
SECTIONS
{
/* Output section .text starts at address 3G+1M.
* Refer to Using LD, the GNU linker: The Location Counter */
. = 0xC0000000 + 0x100000;
_text = .; /* Text and read-only data */
.text : {
*(.text)
*(.fixup)
*(.gnu.warning)
} = 0x9090
/* Unallocated holes filled with 0x9090, i.e. opcode for "NOP NOP".
* Refer to Using LD, the GNU linker: Optional Section Attributes */
_etext = .; /* End of text section */
.rodata : { *(.rodata) *(.rodata.*) }
.kstrtab : { *(.kstrtab) }
/* Aligned to next 16-bytes boundary.
* Refer to Using LD, the GNU linker: Arithmetic Functions */
. = ALIGN(16); /* Exception table */
__start___ex_table = .;
__ex_table : { *(__ex_table) }
__stop___ex_table = .;
__start___ksymtab = .; /* Kernel symbol table */
__ksymtab : { *(__ksymtab) }
__stop___ksymtab = .;
.data : { /* Data */
*(.data)
CONSTRUCTORS
}
/* For "CONSTRUCTORS", refer to
* Using LD, the GNU linker: Option Commands */
_edata = .; /* End of data section */
. = ALIGN(8192); /* init_task */
.data.init_task : { *(.data.init_task) }
. = ALIGN(4096); /* Init code and data */
__init_begin = .;
.text.init : { *(.text.init) }
.data.init : { *(.data.init) }
. = ALIGN(16);
__setup_start = .;
.setup.init : { *(.setup.init) }
__setup_end = .;
__initcall_start = .;
.initcall.init : { *(.initcall.init) }
__initcall_end = .;
. = ALIGN(4096);
__init_end = .;
. = ALIGN(4096);
.data.page_aligned : { *(.data.idt) }
. = ALIGN(32);
.data.cacheline_aligned : { *(.data.cacheline_aligned) }
__bss_start = .; /* BSS */
.bss : {
*(.bss)
}
_end = . ;
/* Output section /DISCARD/ will not be included in the final link output.
* Refer to Using LD, the GNU linker: Section Definitions */
/* Sections to be discarded */
/DISCARD/ : {
*(.text.exit)
*(.data.exit)
*(.exitcall.exit)
}
/* The following output sections are addressed at memory location 0.
* Refer to Using LD, the GNU linker: Optional Section Attributes */
/* Stabs debugging sections. */
.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) }
} |
linux/arch/i386/Makefile被包含在linux/Makefile以提供 i386 特定的项目和术语。
以下所有目标都依赖于linux/Makefile的 vmlinux 目标。 它们通过在linux/arch/i386/boot/Makefile中使用一些选项来完成。
表 1. linux/arch/i386/Makefile 中的目标
| 目标 | 命令 |
|---|---|
| zImage [a] | @$(MAKE) -C arch/i386/boot zImage [b] |
| bzImage | @$(MAKE) -C arch/i386/boot bzImage |
| zlilo | @$(MAKE) -C arch/i386/boot BOOTIMAGE=zImage zlilo |
| bzlilo | @$(MAKE) -C arch/i386/boot BOOTIMAGE=bzImage zlilo |
| zdisk | @$(MAKE) -C arch/i386/boot BOOTIMAGE=zImage zdisk |
| bzdisk | @$(MAKE) -C arch/i386/boot BOOTIMAGE=bzImage zdisk |
| install | @$(MAKE) -C arch/i386/boot BOOTIMAGE=bzImage install |
| 注释 a. zImage 别名:compressed; b. "-C" 是一个 MAKE 命令行选项,用于在读取 makefile 之前更改目录; 请参阅 GNU make:选项摘要 和 GNU make:make 的递归使用。 | |
值得注意的是,此 makefile 重新定义了一些由linux/Makefile导出的环境变量,特别是
OBJCOPY=$(CROSS_COMPILE)objcopy -O binary -R .note -R .comment -S |
不确定为什么 $(LIBS) 两次包含 "$(TOPDIR)/arch/i386/lib/lib.a"
LIBS := $(TOPDIR)/arch/i386/lib/lib.a $(LIBS) $(TOPDIR)/arch/i386/lib/lib.a |
linux/arch/i386/boot/Makefile在某种程度上是独立的,因为它既不被linux/arch/i386/Makefile也不被linux/Makefile.
包含。 但是,它们确实存在一些关系
linux/Makefile:提供常驻内核镜像linux/vmlinux;
linux/arch/i386/boot/Makefile:提供引导程序;
linux/arch/i386/Makefile:确保linux/vmlinux在引导程序构建之前准备就绪,并将目标(如 bzImage)导出到linux/Makefile.
$(BOOTIMAGE) 值,用于目标 zdisk, zlilo 或 zdisk,来自linux/arch/i386/Makefile.
表 2. linux/arch/i386/boot/Makefile 中的目标
| 目标 | 命令 | |
|---|---|---|
| zImage |
| |
| bzImage |
| |
| zdisk |
| |
| zlilo |
| |
| install |
|
表 3. linux/arch/i386/boot/Makefile 中的支持目标
| 目标:先决条件 | 命令 |
|---|---|
| compressed/vmlinux: linux/vmlinux | @$(MAKE) -C compressed vmlinux |
| compressed/bvmlinux: linux/vmlinux | @$(MAKE) -C compressed bvmlinux |
| tools/build: tools/build.c | $(HOSTCC) $(HOSTCFLAGS) -o $@ $< -I$(TOPDIR)/include [a] |
| bootsect: bootsect.o | $(LD) -Ttext 0x0 -s --oformat binary bootsect.o [b] |
| bootsect.o: bootsect.s | $(AS) -o $@ $< |
| bootsect.s: bootsect.S ... | $(CPP) $(CPPFLAGS) -traditional $(SVGA_MODE) $(RAMDISK) $< -o $@ |
| bbootsect: bbootsect.o | $(LD) -Ttext 0x0 -s --oformat binary $< -o $@ |
| bbootsect.o: bbootsect.s | $(AS) -o $@ $< |
| bbootsect.s: bootsect.S ... | $(CPP) $(CPPFLAGS) -D__BIG_KERNEL__ -traditional $(SVGA_MODE) $(RAMDISK) $< -o $@ |
| setup: setup.o | $(LD) -Ttext 0x0 -s --oformat binary -e begtext -o $@ $< |
| setup.o: setup.s | $(AS) -o $@ $< |
| setup.s: setup.S video.S ... | $(CPP) $(CPPFLAGS) -D__ASSEMBLY__ -traditional $(SVGA_MODE) $(RAMDISK) $< -o $@ |
| bsetup: bsetup.o | $(LD) -Ttext 0x0 -s --oformat binary -e begtext -o $@ $< |
| bsetup.o: bsetup.s | $(AS) -o $@ $< |
| bsetup.s: setup.S video.S ... | $(CPP) $(CPPFLAGS) -D__BIG_KERNEL__ -D__ASSEMBLY__ -traditional $(SVGA_MODE) $(RAMDISK) $< -o $@ |
| 注释 a. "$@" 表示目标,"$<" 表示第一个先决条件; 请参阅 GNU make:自动变量; b. "--oformat binary" 要求原始二进制输出,这与可执行文件的内存转储相同; 请参阅 使用 LD,GNU 链接器:命令行选项。 | |
此 makefile 处理镜像(解)压缩机制。
将(解)压缩与引导程序分开是很好的做法。 这种分而治之的解决方案使我们能够轻松改进(解)压缩机制或采用新的引导程序方法。
目录linux/arch/i386/boot/compressed/包含两个源文件head.S以及misc.c.
表 4. linux/arch/i386/boot/compressed/Makefile 中的目标
| 目标 | 命令 | |
|---|---|---|
| vmlinux[a] | $(LD) -Ttext 0x1000 -e startup_32 -o vmlinux head.o misc.o piggy.o | |
| bvmlinux | $(LD) -Ttext 0x100000 -e startup_32 -o bvmlinux head.o misc.o piggy.o | |
| head.o | $(CC) $(AFLAGS) -traditional -c head.S | |
| misc.o |
| |
| piggy.o |
| |
| 注释 a. 此处的 vmlinux 目标与linux/Makefile; b. “subst” 是一个 MAKE 函数; 请参阅 GNU make:用于字符串替换和分析的函数。 | ||
piggy.o中定义的变量 input_len 和 gzippedlinux/vmlinux。 input_len 位于piggy.o的开头,它等于piggy.o的大小,不包括 input_len 本身。 有关 piggy.o 链接器脚本中的 "LONG(expression)",请参阅 使用 LD,GNU 链接器:节数据表达式。
确切地说,被 gzipped 的不是linux/vmlinux本身(ELF 格式),而是它的二进制镜像,它由 objcopy 命令生成。 请注意,$(OBJCOPY) 已被linux/arch/i386/Makefile在 第 2.3 节 中重新定义,以使用 "-O binary" 选项输出原始二进制文件。
当链接 {bootsect, setup} 或 {bbootsect, bsetup} 时,$(LD) 指定 "--oformat binary" 选项以二进制格式输出它们。 当制作 zImage(或 bzImage)时,$(OBJCOPY) 也从 compressed/vmlinux(或 compressed/bvmlinux)生成中间二进制输出。 zImage 或 bzImage 中的所有组件都必须采用原始二进制格式,这一点至关重要,以便镜像可以自行运行,而无需加载器来加载和重定位它。
vmlinux 和 bvmlinux 都在head.o以及misc.o之前预置了piggy.o,但它们是针对不同的起始地址(0x1000 与 0x100000)链接的。
linux/arch/i386/tools/build.c是一个用于生成 zImage 或 bzImage 的主机实用程序。
在linux/arch/i386/boot/Makefile:
tools/build bootsect setup compressed/vmlinux.out $(ROOT_DEV) > zImage tools/build -b bbootsect bsetup compressed/bvmlinux.out $(ROOT_DEV) > bzImage |
tools/build 将以下组件输出到 stdout,stdout 被重定向到 zImage 或 bzImage
bootsect 或 bbootsect:来自linux/arch/i386/boot/bootsect.S,512 字节;
setup 或 bsetup:来自linux/arch/i386/boot/setup.S,4 个扇区或更多,扇区对齐;
compressed/vmlinux.out 或 compressed/bvmlinux.out,包括
head.o:来自linux/arch/i386/boot/compressed/head.S;
misc.o:来自linux/arch/i386/boot/compressed/misc.c;
piggy.o:来自 input_len 和 gzippedlinux/vmlinux.
tools/build 将在输出到 stdout 时更改 bootsect 或 bbootsect 的某些内容
表 5. tools/build 所做的修改
| 偏移量 | 字节 | 变量 | 注释 |
|---|---|---|---|
| 1F1 (497) | 1 | setup_sectors | setup 扇区数,>=4 |
| 1F4 (500) | 2 | sys_size | 系统大小(以 16 字节为单位),小端 |
| 1FC (508) | 1 | minor_root | 根设备次要号 |
| 1FD (509) | 1 | major_root | 根设备主要号 |
在以下章节中,compressed/vmlinux 将被称为 vmlinux,compressed/bvmlinux 将被称为 bvmlinux,如果不会造成混淆的话。