2. Linux Makefiles

在研读 Linux 代码之前,我们应该对 Linux 的组成、编译和链接方式有一些基本的了解。实现此目标的一个直接方法是理解 Linux 的 makefile 文件。如果您喜欢在线浏览源代码,请查看 Linux 交叉引用

2.1. linux/Makefile

以下是此顶层 makefile 中的一些常用目标

概述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
"--start-group" 和 "--end-group" 是 ld 命令行选项,用于解决符号引用问题。 有关详细信息,请参阅 使用 LD,GNU 链接器:命令行选项

Rules.make包含在多个 Makefile 之间共享的规则。

2.2. linux/arch/i386/vmlinux.lds

编译后,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) }
}

2.3. linux/arch/i386/Makefile

linux/arch/i386/Makefile被包含在linux/Makefile以提供 i386 特定的项目和术语。

以下所有目标都依赖于linux/Makefilevmlinux 目标。 它们通过在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
效果将传递到子目录 makefile,并将更改工具的行为。 有关 objcopy 命令行选项的详细信息,请参阅 GNU 二进制实用程序:objcopy

不确定为什么 $(LIBS) 两次包含 "$(TOPDIR)/arch/i386/lib/lib.a"
LIBS := $(TOPDIR)/arch/i386/lib/lib.a $(LIBS) $(TOPDIR)/arch/i386/lib/lib.a
它可能用于解决某些工具链的链接问题。

2.4. linux/arch/i386/boot/Makefile

linux/arch/i386/boot/Makefile在某种程度上是独立的,因为它既不被linux/arch/i386/Makefile也不被linux/Makefile.

包含。 但是,它们确实存在一些关系

$(BOOTIMAGE) 值,用于目标 zdisk, zlilozdisk,来自linux/arch/i386/Makefile.

表 2. linux/arch/i386/boot/Makefile 中的目标

目标命令
zImage
$(OBJCOPY) compressed/vmlinux compressed/vmlinux.out
tools/build bootsect setup compressed/vmlinux.out $(ROOT_DEV) > zImage
bzImage
$(OBJCOPY) compressed/bvmlinux compressed/bvmlinux.out
tools/build -b bbootsect bsetup compressed/bvmlinux.out $(ROOT_DEV) \
        > bzImage
zdisk
dd bs=8192 if=$(BOOTIMAGE) of=/dev/fd0
zlilo
if [ -f $(INSTALL_PATH)/vmlinuz ]; then mv $(INSTALL_PATH)/vmlinuz
        $(INSTALL_PATH)/vmlinuz.old; fi
if [ -f $(INSTALL_PATH)/System.map ]; then mv $(INSTALL_PATH)/System.map
        $(INSTALL_PATH)/System.old; fi
cat $(BOOTIMAGE) > $(INSTALL_PATH)/vmlinuz
cp $(TOPDIR)/System.map $(INSTALL_PATH)/
if [ -x /sbin/lilo ]; then /sbin/lilo; else /etc/lilo/install; fi
install
sh -x ./install.sh $(KERNELRELEASE) $(BOOTIMAGE) $(TOPDIR)/System.map
        "$(INSTALL_PATH)"
tools/build 从 {bootsect, setup, compressed/vmlinux.out} 构建引导镜像 zImage,或从 {bbootsect, bsetup, compressed/bvmlinux,out} 构建 bzImagelinux/Makefile"export ROOT_DEV = CURRENT"。 请注意,$(OBJCOPY) 已被linux/arch/i386/Makefile第 2.3 节 中重新定义。

表 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 链接器:命令行选项
请注意,当编译bootsect.Sbbootsect.ssetup.Sbsetup.s时,它具有 "-D__BIG_KERNEL__"。 它们必须是位置无关代码 (PIC),因此 "-Ttext" 选项是什么并不重要。

2.5. linux/arch/i386/boot/compressed/Makefile

此 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
$(CC) $(CFLAGS) -DKBUILD_BASENAME=$(subst $(comma),_,$(subst -,_,$(*F)))
        -c misc.c[b]
piggy.o
tmppiggy=_tmp_$$$$piggy; \
rm -f $$tmppiggy $$tmppiggy.gz $$tmppiggy.lnk; \
$(OBJCOPY) $(SYSTEM) $$tmppiggy; \
gzip -f -9 < $$tmppiggy > $$tmppiggy.gz; \
echo "SECTIONS { .data : { input_len = .; \
        LONG(input_data_end - input_data) input_data = .; \
        *(.data) input_data_end = .; }}" > $$tmppiggy.lnk; \
$(LD) -r -o piggy.o -b binary $$tmppiggy.gz -b elf32-i386 \
        -T $$tmppiggy.lnk; \
rm -f $$tmppiggy $$tmppiggy.gz $$tmppiggy.lnk
注释
a. 此处的 vmlinux 目标与linux/Makefile;
b. “subst” 是一个 MAKE 函数; 请参阅 GNU make:用于字符串替换和分析的函数

piggy.o中定义的变量 input_len 和 gzippedlinux/vmlinuxinput_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)生成中间二进制输出。 zImagebzImage 中的所有组件都必须采用原始二进制格式,这一点至关重要,以便镜像可以自行运行,而无需加载器来加载和重定位它。

vmlinuxbvmlinux 都在head.o以及misc.o之前预置了piggy.o,但它们是针对不同的起始地址(0x1000 与 0x100000)链接的。

2.6. linux/arch/i386/tools/build.c

linux/arch/i386/tools/build.c是一个用于生成 zImagebzImage 的主机实用程序。

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
中,"-b" 表示 is_big_kernel,用于检查系统镜像是否过大。

tools/build 将以下组件输出到 stdout,stdout 被重定向到 zImagebzImage

  1. bootsect 或 bbootsect:来自linux/arch/i386/boot/bootsect.S,512 字节;

  2. setup 或 bsetup:来自linux/arch/i386/boot/setup.S,4 个扇区或更多,扇区对齐;

  3. compressed/vmlinux.out 或 compressed/bvmlinux.out,包括

    1. head.o:来自linux/arch/i386/boot/compressed/head.S;

    2. misc.o:来自linux/arch/i386/boot/compressed/misc.c;

    3. piggy.o:来自 input_len 和 gzippedlinux/vmlinux.

tools/build 将在输出到 stdout 时更改 bootsectbbootsect 的某些内容

表 5. tools/build 所做的修改

偏移量字节变量注释
1F1 (497)1setup_sectorssetup 扇区数,>=4
1F4 (500)2sys_size系统大小(以 16 字节为单位),小端
1FC (508)1minor_root根设备次要号
1FD (509)1major_root根设备主要号

在以下章节中,compressed/vmlinux 将被称为 vmlinux,compressed/bvmlinux 将被称为 bvmlinux,如果不会造成混淆的话。

2.7. 参考