你知道 Linux 内核是如何构建的吗?
作者:网络转载 发布时间:[ 2015/9/14 13:29:39 ] 推荐标签:操作系统 内核
下一个可以被执行的目标如下:
$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
像我们看到的,vmlinux-dir 依赖于两部分:prepare 和 scripts。第一个 prepare 定义在内核的根 makefile 中,准备工作分成三个阶段:
prepare: prepare0
prepare0: archprepare FORCE
$(Q)$(MAKE) $(build)=.
archprepare: archheaders archscripts prepare1 scripts_basic
prepare1: prepare2 $(version_h) include/generated/utsrelease.h /
include/config/auto.conf
$(cmd_crmodverdir)
prepare2: prepare3 outputmakefile asm-generic
第一个 prepare0 展开到 archprepare ,后者又展开到 archheader 和 archscripts,这两个变量定义在 x86_64 相关的 Makefile。让我们看看这个文件。x86_64 特定的 makefile 从变量定义开始,这些变量都是和特定架构的配置文件 (defconfig,等等)有关联。在定义了编译 16-bit 代码的编译选项之后,根据变量 BITS 的值,如果是 32, 汇编代码、链接器、以及其它很多东西(全部的定义都可以在arch/x86/Makefile找到)对应的参数是 i386,而 64 对应的是 x86_84。
第一个目标是 makefile 生成的系统调用列表(syscall table)中的 archheaders :
archheaders:
$(Q)$(MAKE) $(build)=arch/x86/entry/syscalls all
第二个目标是 makefile 里的 archscripts:
archscripts: scripts_basic
$(Q)$(MAKE) $(build)=arch/x86/tools relocs
我们可以看到 archscripts 是依赖于根 Makefile里的scripts_basic 。首先我们可以看出 scripts_basic 是按照 scripts/basic 的 makefile 执行 make 的:
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
scripts/basic/Makefile 包含了编译两个主机程序 fixdep 和 bin2 的目标:
hostprogs-y := fixdep
hostprogs-$(CONFIG_BUILD_BIN2C) += bin2c
always := $(hostprogs-y)
$(addprefix $(obj)/,$(filter-out fixdep,$(always))): $(obj)/fixdep
第一个工具是 fixdep:用来优化 gcc 生成的依赖列表,然后在重新编译源文件的时候告诉make。第二个工具是 bin2c,它依赖于内核配置选项 CONFIG_BUILD_BIN2C,并且它是一个用来将标准输入接口(LCTT 译注:即 stdin)收到的二进制流通过标准输出接口(即:stdout)转换成 C 头文件的非常小的 C 程序。你可能注意到这里有些奇怪的标志,如 hostprogs-y 等。这个标志用于所有的 kbuild 文件,更多的信息你可以从documentation 获得。在我们这里, hostprogs-y 告诉 kbuild 这里有个名为 fixed 的程序,这个程序会通过和 Makefile 相同目录的 fixdep.c 编译而来。
执行 make 之后,终端的第一个输出是 kbuild 的结果:
$ make
HOSTCC scripts/basic/fixdep
当目标 script_basic 被执行,目标 archscripts 会 make arch/x86/tools 下的 makefile 和目标 relocs:
$(Q)$(MAKE) $(build)=arch/x86/tools relocs
包含了重定位 的信息的代码 relocs_32.c 和 relocs_64.c 将会被编译,这可以在make 的输出中看到:
HOSTCC arch/x86/tools/relocs_32.o
HOSTCC arch/x86/tools/relocs_64.o
HOSTCC arch/x86/tools/relocs_common.o
HOSTLD arch/x86/tools/relocs
在编译完 relocs.c 之后会检查 version.h:
$(version_h): $(srctree)/Makefile FORCE
$(call filechk,version.h)
$(Q)rm -f $(old_version_h)
我们可以在输出看到它:
CHK include/config/kernel.release
以及在内核的根 Makefiel 使用 arch/x86/include/generated/asm 的目标 asm-generic 来构建 generic 汇编头文件。在目标 asm-generic 之后,archprepare 完成了,所以目标 prepare0 会接着被执行,如我上面所写:
prepare0: archprepare FORCE
$(Q)$(MAKE) $(build)=.
注意 build,它是定义在文件 scripts/Kbuild.include,内容是这样的:
build := -f $(srctree)/scripts/Makefile.build obj
或者在我们的例子中,它是当前源码目录路径:.:
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=.
脚本 scripts/Makefile.build 通过参数 obj 给定的目录找到 Kbuild 文件,然后引入 kbuild 文件:
include $(kbuild-file)
并根据这个构建目标。我们这里 . 包含了生成 kernel/bounds.s 和 arch/x86/kernel/asm-offsets.s 的 Kbuild 文件。在此之后,目标 prepare 完成了它的工作。 vmlinux-dirs 也依赖于第二个目标 scripts ,它会编译接下来的几个程序:filealias,mk_elfconfig,modpost 等等。之后,scripts/host-programs 可以开始编译我们的目标 vmlinux-dirs 了。
首先,我们先来理解一下 vmlinux-dirs 都包含了那些东西。在我们的例子中它包含了下列内核目录的路径:
init usr arch/x86 kernel mm fs ipc security crypto block
drivers sound firmware arch/x86/pci arch/x86/power
arch/x86/video net lib arch/x86/lib
我们可以在内核的根 Makefile 里找到 vmlinux-dirs 的定义:
vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) /
$(core-y) $(core-m) $(drivers-y) $(drivers-m) /
$(net-y) $(net-m) $(libs-y) $(libs-m)))
init-y := init/
drivers-y := drivers/ sound/ firmware/
net-y := net/
libs-y := lib/
...
...
...
这里我们借助函数 patsubst 和 filter去掉了每个目录路径里的符号 /,并且把结果放到 vmlinux-dirs 里。所以我们有了 vmlinux-dirs 里的目录列表,以及下面的代码:
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
符号 $@ 在这里代表了 vmlinux-dirs,这表明程序会递归遍历从 vmlinux-dirs 以及它内部的全部目录(依赖于配置),并且在对应的目录下执行 make 命令。我们可以在输出看到结果:
CC init/main.o
CHK include/generated/compile.h
CC init/version.o
CC init/do_mounts.o
...
CC arch/x86/crypto/glue_helper.o
AS arch/x86/crypto/aes-x86_64-asm_64.o
CC arch/x86/crypto/aes_glue.o
...
AS arch/x86/entry/entry_64.o
AS arch/x86/entry/thunk_64.o
CC arch/x86/entry/syscall_64.o
每个目录下的源代码将会被编译并且链接到 built-io.o 里:
$ find . -name built-in.o
./arch/x86/crypto/built-in.o
./arch/x86/crypto/sha-mb/built-in.o
./arch/x86/net/built-in.o
./init/built-in.o
./usr/built-in.o
...
...
好了,所有的 built-in.o 都构建完了,现在我们回到目标 vmlinux 上。你应该还记得,目标 vmlinux 是在内核的根makefile 里。在链接 vmlinux 之前,系统会构建 samples, Documentation 等等,但是如上文所述,我不会在本文描述这些。
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
...
...
+$(call if_changed,link-vmlinux)
你可以看到,调用脚本 scripts/link-vmlinux.sh 的主要目的是把所有的 built-in.o 链接成一个静态可执行文件,和生成 System.map。 后我们来看看下面的输出:
LINK vmlinux
LD vmlinux.o
MODPOST vmlinux.o
GEN .version
CHK include/generated/compile.h
UPD include/generated/compile.h
CC init/version.o
LD init/built-in.o
KSYM .tmp_kallsyms1.o
KSYM .tmp_kallsyms2.o
LD vmlinux
SORTEX vmlinux
SYSMAP System.map
vmlinux 和System.map 生成在内核源码树根目录下。
$ ls vmlinux System.map
System.map vmlinux
这是全部了,vmlinux 构建好了,下一步是创建 bzImage.
制作bzImage
bzImage 是压缩了的 linux 内核镜像。我们可以在构建了 vmlinux 之后通过执行 make bzImage 获得bzImage。同时我们可以仅仅执行 make 而不带任何参数也可以生成 bzImage ,因为它是在 arch/x86/kernel/Makefile 里预定义的、默认生成的镜像:
all: bzImage
让我们看看这个目标,它能帮助我们理解这个镜像是怎么构建的。我已经说过了 bzImage 是被定义在 arch/x86/kernel/Makefile,定义如下:
bzImage: vmlinux
$(Q)$(MAKE) $(build)=$(boot) $(KBUILD_IMAGE)
$(Q)mkdir -p $(objtree)/arch/$(UTS_MACHINE)/boot
$(Q)ln -fsn ../../x86/boot/bzImage $(objtree)/arch/$(UTS_MACHINE)/boot/$@
在这里我们可以看到第一次为 boot 目录执行 make,在我们的例子里是这样的:
boot := arch/x86/boot
现在的主要目标是编译目录 arch/x86/boot 和 arch/x86/boot/compressed 的代码,构建 setup.bin 和 vmlinux.bin,后用这两个文件生成 bzImage。第一个目标是定义在 arch/x86/boot/Makefile 的 $(obj)/setup.elf:
$(obj)/setup.elf: $(src)/setup.ld $(SETUP_OBJS) FORCE
$(call if_changed,ld)
我们已经在目录 arch/x86/boot 有了链接脚本 setup.ld,和扩展到 boot 目录下全部源代码的变量 SETUP_OBJS 。我们可以看看第一个输出:
AS arch/x86/boot/bioscall.o
CC arch/x86/boot/cmdline.o
AS arch/x86/boot/copy.o
HOSTCC arch/x86/boot/mkcpustr
CPUSTR arch/x86/boot/cpustr.h
CC arch/x86/boot/cpu.o
CC arch/x86/boot/cpuflags.o
CC arch/x86/boot/cpucheck.o
CC arch/x86/boot/early_serial_console.o
CC arch/x86/boot/edd.o
下一个源码文件是 arch/x86/boot/header.S,但是我们不能现在编译它,因为这个目标依赖于下面两个头文件:
$(obj)/header.o: $(obj)/voffset.h $(obj)/zoffset.h
第一个头文件 voffset.h 是使用 sed 脚本生成的,包含用 nm 工具从 vmlinux 获取的两个地址:
#define VO__end 0xffffffff82ab0000
#define VO__text 0xffffffff81000000
这两个地址是内核的起始和结束地址。第二个头文件 zoffset.h 在 arch/x86/boot/compressed/Makefile 可以看出是依赖于目标 vmlinux的:
$(obj)/zoffset.h: $(obj)/compressed/vmlinux FORCE
$(call if_changed,zoffset)
目标 $(obj)/compressed/vmlinux 依赖于 vmlinux-objs-y —— 说明需要编译目录 arch/x86/boot/compressed 下的源代码,然后生成 vmlinux.bin、vmlinux.bin.bz2,和编译工具 mkpiggy。我们可以在下面的输出看出来:
LDS arch/x86/boot/compressed/vmlinux.lds
AS arch/x86/boot/compressed/head_64.o
CC arch/x86/boot/compressed/misc.o
CC arch/x86/boot/compressed/string.o
CC arch/x86/boot/compressed/cmdline.o
OBJCOPY arch/x86/boot/compressed/vmlinux.bin
BZIP2 arch/x86/boot/compressed/vmlinux.bin.bz2
HOSTCC arch/x86/boot/compressed/mkpiggy
vmlinux.bin 是去掉了调试信息和注释的 vmlinux 二进制文件,加上了占用了 u32 (LCTT 译注:即4-Byte)的长度信息的 vmlinux.bin.all 压缩后是 vmlinux.bin.bz2。其中 vmlinux.bin.all 包含了 vmlinux.bin 和vmlinux.relocs(LCTT 译注:vmlinux 的重定位信息),其中 vmlinux.relocs 是 vmlinux 经过程序 relocs 处理之后的 vmlinux 镜像(见上文所述)。我们现在已经获取到了这些文件,汇编文件 piggy.S 将会被 mkpiggy 生成、然后编译:
MKPIGGY arch/x86/boot/compressed/piggy.S
AS arch/x86/boot/compressed/piggy.o
这个汇编文件会包含经过计算得来的、压缩内核的偏移信息。处理完这个汇编文件,我们可以看到 zoffset 生成了:
ZOFFSET arch/x86/boot/zoffset.h
现在 zoffset.h 和 voffset.h 已经生成了,arch/x86/boot 里的源文件可以继续编译:
AS arch/x86/boot/header.o
CC arch/x86/boot/main.o
CC arch/x86/boot/mca.o
CC arch/x86/boot/memory.o
CC arch/x86/boot/pm.o
AS arch/x86/boot/pmjump.o
CC arch/x86/boot/printf.o
CC arch/x86/boot/regs.o
CC arch/x86/boot/string.o
CC arch/x86/boot/tty.o
CC arch/x86/boot/video.o
CC arch/x86/boot/video-mode.o
CC arch/x86/boot/video-vga.o
CC arch/x86/boot/video-vesa.o
CC arch/x86/boot/video-bios.o
所有的源代码会被编译,他们终会被链接到 setup.elf :
LD arch/x86/boot/setup.elf
或者:
ld -m elf_x86_64 -T arch/x86/boot/setup.ld arch/x86/boot/a20.o arch/x86/boot/bioscall.o arch/x86/boot/cmdline.o arch/x86/boot/copy.o arch/x86/boot/cpu.o arch/x86/boot/cpuflags.o arch/x86/boot/cpucheck.o arch/x86/boot/early_serial_console.o arch/x86/boot/edd.o arch/x86/boot/header.o arch/x86/boot/main.o arch/x86/boot/mca.o arch/x86/boot/memory.o arch/x86/boot/pm.o arch/x86/boot/pmjump.o arch/x86/boot/printf.o arch/x86/boot/regs.o arch/x86/boot/string.o arch/x86/boot/tty.o arch/x86/boot/video.o arch/x86/boot/video-mode.o arch/x86/boot/version.o arch/x86/boot/video-vga.o arch/x86/boot/video-vesa.o arch/x86/boot/video-bios.o -o arch/x86/boot/setup.elf
后的两件事是创建包含目录 arch/x86/boot/* 下的编译过的代码的 setup.bin:
objcopy -O binary arch/x86/boot/setup.elf arch/x86/boot/setup.bin
以及从 vmlinux 生成 vmlinux.bin :
objcopy -O binary -R .note -R .comment -S arch/x86/boot/compressed/vmlinux arch/x86/boot/vmlinux.bin
后,我们编译主机程序 arch/x86/boot/tools/build.c,它将会用来把 setup.bin 和 vmlinux.bin 打包成 bzImage:
arch/x86/boot/tools/build arch/x86/boot/setup.bin arch/x86/boot/vmlinux.bin arch/x86/boot/zoffset.h arch/x86/boot/bzImage
实际上 bzImage 是把 setup.bin 和 vmlinux.bin 连接到一起。终我们会看到输出结果,和那些用源码编译过内核的同行的结果一样:
Setup is 16268 bytes (padded to 16384 bytes).
System is 4704 kB
CRC 94a88f9a
Kernel: arch/x86/boot/bzImage is ready (#5)
全部结束。
结论
这是本文的结尾部分。本文我们了解了编译内核的全部步骤:从执行 make 命令开始,到后生成 bzImage。我知道,linux 内核的 makefile 和构建 linux 的过程第一眼看起来可能比较迷惑,但是这并不是很难。希望本文可以帮助你理解构建 linux 内核的整个流程。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系SPASVO小编(021-61079698-8054),我们将立即处理,马上删除。
相关推荐
Linux下开源的DDR压力测试工具曝Linux恶意软件:让树莓派设备挖掘数字货币linux系统中不同颜色的文件夹及根目录介绍软件测试工程师必知必会Linux命令Linux下DNS服务器配置如何成为不可替代的Linux运维工程师?详解Linux进程(作业)的查看和杀死Linux 日志定时轮询流程详解比特币勒索病毒不只Windows系统有,Linux版的来了Linux日志定时轮询流程详解Linux iommu和vfio概念空间解构Linux系统如何低于TCP洪水攻击Linux无损调整分区大小Linux下防火墙配置实例Linux使用Jexus托管Asp.Net Core应用程序Linux中引号的那些事

sales@spasvo.com