转载于 https://book.2cto.com/201309/33426.html

构建vmlinux.bin的规则在arch/x86/boot目录下的Makefile中:

/arch/x86/boot/Makefile:
OBJCOPYFLAGS_vmlinux.bin := -O binary -R .note -R .comment -S
$(obj)/vmlinux.bin: $(obj)/compressed/vmlinux FORCE$(call if_changed,objcopy)

根据前面对kbuild自定义函数if_changed的讨论可知,这里将执行命令cmd_objcopy。cmd_objcopy的定义如下:

/scripts/Makefile.lib:
cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) \$< $@

其中OBJCOPY就是二进制工具objcopy,当然,因为我们使用的是交叉工具链,所以objcopy是i686-none-linux-gnu-objcopy,前面构建工具链时构建组件Binutils时已经构建:

/Makefile:
OBJCOPY     = $(CROSS_COMPILE)objcopy

这里使用这个工具的目的是将ELF格式的文件转化为裸二进制格式。

cmd_objcopy中的“&lt;”、“&lt;”、“<”、“@”、“$(@F)”都是make的自动变量:

“$<”表示规则的依赖列表中的第一个依赖,这里是arch/x86/boot/compressed/vmlinux;

“$@”表示规则的目标,这里是arch/x86/boot/vmlinux.bin;

“$(@F)”表示构建目标去除目录后的文件名,这里是vmlinux.bin;

因此变量 OBJCOPYFLAGS_$(@F)展开为OBJCOPYFLAGS_ vmlinux.bin。替换各个变量后,cmd_objcopy最后展开大致为:

cmd_objcopy = i686-none-linux-gnu-objcopy -O binary -R .note \-R .comment -S arch/x86/boot/compressed/vmlinux \arch/x86/boot/vmlinux.bin

上述代码的意义已经显而易见了:arch/x86/boot目录下的vmlinux.bin是由arch/x86/boot/compressed目录下的vmlinux通过工具i686-none-linux-gnu-objcopy复制而来。

为了指导加载器加载ELF文件,ELF文件中附加了很多信息,如ELF文件头、Program Header Table、符号表、重定位表等。但是这些对内核是没有意义的,Bootloader加载内核时不需要ELF文件中附加的这些信息,变量OBJCOPYFLAGS_vmlinux.bin中的“-O binary”指定objcopy将复制后内核转换为裸二进制格式;选项(如“.note”、“.comment”)则表明将这些段也删除。读者可能会问,转化为裸二进制格式时还会保留如“.note”、“.comment”等段吗?当然了,转化为裸二进制格式只不过把为ELF格式附加的东西去除掉了,比如ELF的头、Section Header Table、Program Header Table等,但是并不会删除保存具体内容的段。

显然,构建的焦点转换为arch/x86/boot/compressed下的vmlinux,其构建规则如下:

/arch/x86/boot/Makefile:
$(obj)/compressed/vmlinux: FORCE$(Q)$(MAKE) $(build)=$(obj)/compressed $@

构建命令展开为:
make -f scripts/Makefile.build obj=arch/x86/boot/compressed
arch/x86/boot/compressed/vmlinux

Makefile.build将arch/x86/boot/compressed目录下的Makefile包含到Makefile.build中,生成完整的Makefile。但是这次,make没有如同构建各个子目录一样使用默认的构建目标,而是指定了构建目标为arch/x86/boot/compressed/vmlinux,其构建规则在arch/x86/boot/compressed目录下的Makefile中定义:

/arch/x86/boot/compressed/Makefile:
VMLINUX_OBJS = $(obj)/vmlinux.lds $(obj)/head_$(BITS).o \$(obj)/misc.o $(obj)/string.o $(obj)/cmdline.o \$(obj)/early_serial_console.o $(obj)/piggy.o
...
$(obj)/vmlinux: $(VMLINUX_OBJS) FORCE$(call if_changed,ld)

对于32位系统,变量BITS为32。由上述Makefile可见,arch/x86/boot/compressed目录下的vmlinux是由该目录下的head_32.o、misc.o、string.o 、cmdline.o 、early_serial_console.o 以及piggy.o链接而成的。其中vmlinux.lds是指导链接过程的脚本。

在一份刚刚解压且没有进行任何编译动作之前的内核源码中,除了piggy.o,我们可以找到上述依赖列表中任何一个目标文件的源文件,比如head_32.o对应源文件head_32.S,misc.o对应源文件misc.c等。而我们却找不到piggy.o对应的源文件,比如piggy.c或piggy.S亦或其他。但是仔细观察,我们会发现在arch/x86/boot/compressed目录下的Makefile中有一个创建文件piggy.S的规则:

/arch/x86/boot/compressed/Makefile:
cmd_mkpiggy = $(obj)/mkpiggy $< > $@ || ( rm -f $@ ; false )$(obj)/piggy.S: $(obj)/vmlinux.bin.$(suffix-y) $(obj)/mkpiggy \FORCE$(call if_changed,mkpiggy)

看到上面的规则,我们恍然大悟,原来piggy.o是由piggy.S汇编而来,而piggy.S是编译内核时动态创建的,这就是我们找不到它的原因。piggy.S的第一个依赖vmlinux.bin.$(suffix-y)中的suffix-y表示内核压缩方式对应的后缀:

/arch/x86/boot/compressed/Makefile:
suffix-$(CONFIG_KERNEL_GZIP)    := gz
suffix-$(CONFIG_KERNEL_BZIP2)   := bz2

如果配置内核时指定采用gzip压缩方式,则suffix-y值为gz;如果指定bzip2压缩方式,则suffix-y值为bz2;等等。在本书中,我们配置的内核使用默认的压缩方式gzip,因此,suffix-y的值为gz。那么vmlinux.bin.gz是什么呢?我们看下面的脚本:

/arch/x86/boot/compressed/Makefile:
vmlinux.bin.all-y := $(obj)/vmlinux.bin
vmlinux.bin.all-$(CONFIG_X86_NEED_RELOCS) += \$(obj)/vmlinux.relocs
$(obj)/vmlinux.bin.gz: $(vmlinux.bin.all-y) FORCE$(call if_changed,gzip)

看到这里,相信读者已经不需要看cmd_gzip的定义了。根据变量vmlinux.bin.all-y的值,vmlinux.bin.all-y中包括arch/x86/boot/compressed目录下的vmlinux.bin。如果内核被配置为可重定位的,那么vmlinux.bin.all-y中还包括记录重定位信息的vmlinux.relocs。也就是说,如果内核被配置可重定位,则vmlinux.bin.gz是由vmlinux.bin和vmlinux.relocs压缩而来的,否则只是vmlinux.bin由压缩而来。

那么arch/x86/boot/compressed目录下的vmlinux.bin又是如何创建的?看下面的脚本:

/arch/arch/x86/boot/compressed/Makefile:
OBJCOPYFLAGS_vmlinux.bin :=  -R .comment -S
$(obj)/vmlinux.bin: vmlinux FORCE$(call if_changed,objcopy)

我们再次看到了熟悉的objcopy,也就是说,arch/x86/boot/compressed目录下的vmlinux.bin是由vmlinux复制而来。而vmlinux没有任何修饰前缀,这说明其就是最顶层目录下的有效载荷。但是在这里我们也看到,这次复制过程只是删除了“.comment”段,以及符号表和重定位表(通过参数-S指定),而有效载荷vmlinux的格式依然是ELF格式的,如果不需要使用ELF格式的内核,这里追加一个“-O binary”即可。

接着我们再来看构建目标piggy.S的命令。进行变量替换后,cmd_mkpiggy展开为:

cmd_mkpiggy = arch/x86/boot/compressed/mkpiggy \arch/x86/boot/compressed/vmlinux.bin.gz \> arch/x86/boot/compressed/piggy.S

其中mkpiggy是内核自带的一个工具程序,源码如下:

/arch/x86/boot/compressed/mkpiggy.c:int main(int argc, char *argv[])
{...printf(".section \".rodata..compressed\",\"a\",\@progbits\n");printf(".globl z_input_len\n");printf("z_input_len = %lu\n", ilen);printf(".globl z_output_len\n");printf("z_output_len = %lu\n", (unsigned long)olen);printf(".globl z_extract_offset\n");printf("z_extract_offset = 0x%lx\n", offs);...printf(".incbin \"%s\"\n", argv[1]);...
}

mkpiggy向屏幕打印了一堆文本。习惯上,我们会认为标准输出就是屏幕,但是回头再仔细观察一下cmd_mkpiggy的定义,其将标准输出重定向到了文件piggy.S,所以这里printf实际上是在组织汇编语句,然后输出到piggy.S中。也就是说,mkpiggy就是在“写”一个汇编程序。

根据代码可见,这个piggy.S非常简单,其使用汇编指令incbin将压缩的有效载荷vmlinux.bin.gz不加更改地直接包含进来。除了包含了压缩的内核映像外,piggy.S中还定义了解压vmlinux.bin.gz时需要的各种信息,包括压缩映像的长度、解压后的长度等,在解压内核时,解压代码将需要这些信息。下面是mkpiggy生成的一个具体的piggy.S示例:

.section ".rodata..compressed","a",@progbits
.globl z_input_len
z_input_len = 1721557
.globl z_output_len
z_output_len = 3421472
.globl z_extract_offset
z_extract_offset = 0x1b0000
.globl z_extract_offset_negative
z_extract_offset_negative = -0x1b0000
.globl input_data, input_data_end
input_data:
.incbin "arch/x86/boot/compressed/vmlinux.bin.gz"
input_data_end:

终于结束了这个让人眩晕的过程,让我们来回顾一下vmlinux.bin的构建过程:

1)kbuild使用objcopy,将顶层Makefile构建好的内核映像vmlinux复制到arch/x86/boot/compressed目录下,删除了“.comment”段、符号表和重定位表,并命名为vmlinux.bin;

2)kbuild压缩内核映像vmlinux.bin,笔者采用默认的压缩方式gzip,所以压缩后的内核映像为vmlinux.bin.gz;

3)kbuild借助内核自带的程序mkpiggy构建一个汇编程序piggy.S,该汇编程序就是vmlinux.bin.gz加上一些解压内核时需要的信息;

4)kbuild将head_32.o、misc.o以及包含压缩映像的piggy.o等目标文件链接为vmlinux.bin,保存到arch/x86/boot目录下。

可见,vmlinux.bin由压缩的vmlinux加上以head_32.o为代表的一小部分非压缩代码组成。

Linux内核构建之vmlinux.bin相关推荐

  1. Linux内核构建系统之六

    转自:http://www.juliantec.info/julblog/yihect/linux-kernel-build-system-6 Linux内核构建系统之六 yihect | 10 元月 ...

  2. Linux内核构建与开发

    Linux内核构建与开发 rtoax 2021年3月 1. Linux 内核的构建 1.1. 介绍 我不会告诉你怎么在自己的电脑上去构建.安装一个定制化的 Linux 内核,这样的资料太多了,它们会对 ...

  3. Linux内核映像vmlinux、Image、zImage、uImage区别

    本文介绍几种常用的Linux内核映像的区别. 一.vmlinux vmlinux:Linux内核编译出来的原始的内核文件,elf格式,未做压缩处理. 该映像可用于定位内核问题,但不能直接引导Linux ...

  4. 直播回顾:如何基于Linux内核构建起商用密码基础设施?| 龙蜥技术

    编者按:本文整理自龙蜥大讲堂技术解读,分享主题为<构建商用密码操作系统>,直播视频回放已上线至龙蜥社区官网(文末阅读原文直接跳转):首页-支持-视频,欢迎观看. 作者张天佳,来⾃阿⾥云操作 ...

  5. linux内核链接脚本vmlinux.lds分析(十一)

    vmlinux.lds.S主要是用来组织内核的每个函数存放在内核镜像文件的位置.编译内核源码生成内核文件的过程分两步,一个是"编译",另一个是"链接"的过程,v ...

  6. Linux 内核构建

    文章目录 前言 构建Linux内核 Linux内核文件组织结构 Linux内核文件说明 Linux内核的makefile文件 Make命令 内核源代码的获取方式 编译内核过程 第1步,预处理 第2步, ...

  7. 编译linux内核报错,/bin/sh: 1: flex: not foundscripts/Makefile.host:9: recipe for target ‘scripts/kconfig/

    linux kernel 编译报错,执行make menuconfig,报错如下: HOSTCC  scripts/basic/fixdep   UPD     scripts/kconfig/mco ...

  8. java的linux内核构建,构建一个Docker 的Java编译环境

    用Dockerfile 构建一个Java的编译环境 1.包括以下软件包 Ubuntu jdk maven svn 2.jdk.maven 需要手动下载,下载之后分别为 jdk-8u51-linux-x ...

  9. 3.4.3 深度探索linux,3.2.4 vmlinux.bin的构建过程(3)

    3.2.4 vmlinux.bin的构建过程(3) mkpiggy向屏幕打印了一堆文本.习惯上,我们会认为标准输出就是屏幕,但是回头再仔细观察一下cmd_mkpiggy的定义,其将标准输出重定向到了文 ...

最新文章

  1. Pandas | 5 种技巧高效利用value-counts
  2. Java super关键字
  3. 实现ftp_FTP文件服务器的实现
  4. 洛谷P3919可持久化线段树
  5. leetcode[217].存在重复元素
  6. 测试龙芯 LoongArch .NET之 使用 FastTunnel 做内网穿透远程计算机
  7. “约见”面试官系列之常见面试题之第五十一篇之CSS Sprites(建议收藏)
  8. 单边指数信号的特点_今日股市分析:上证指数若能守住3400,蓄力反弹就有戏...
  9. 《汇编语言》王爽—第五章实验三详解
  10. 并发测试工具_性能测试工具基本工作原理及基本操作流程
  11. 不能忽视 php warning
  12. Spring之AntPathMatcher
  13. htmlh1 h6,HTML 5 h1 至 h6 标签 - HTML 参考手册
  14. Controller类中方法返回值详解
  15. vue 判断设备是手机端还是pc端
  16. 在word中一个符号怎么打,这个符号是上边一个白三角,下边一个黑三角,两个三角对称形成一个向右的箭头。
  17. mysql容灾备份和恢复_关于容灾备份和恢复
  18. 使用swing换一个国际象棋棋盘的实例
  19. LFS学习系列3 — 前言
  20. 计算机毕业设计Java学校食堂管理(源码+系统+mysql数据库+lw文档)

热门文章

  1. ZBrush常见问题
  2. 重型工业机械设备远程监控解决方案
  3. 干货:蓝海创意云制作液体教程来啦~
  4. php crypt,PHP crypt() 函数
  5. 基于STM32的半导体制冷片(TEC)温度控制系统设计
  6. 【C语言】如何自动控制输出空格的个数
  7. threejs 绘制球体_javascript – 使用ThreeJS获取球体纹理上的点击位置
  8. 上海科达(科远)科技测试工程师面试题
  9. 取消github信用卡绑定
  10. QT中对Pro和Pri的解疑