linux内核vmlinux的编译过程(七)
一. vmlinux目标及其构建规则
定义在顶层Makefile中,如下:
# The all: target is the default when no target is given on the # command line. # This allow a user to issue only 'make' to build a kernel including modules # Defaults to vmlinux, but the arch makefile usually adds further targets all: vmlinux ... # vmlinux image - including updated kernel symbols vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE ifdef CONFIG_HEADERS_CHECK $(Q)$(MAKE) -f $(srctree)/Makefile headers_check endif ifdef CONFIG_SAMPLES $(Q)$(MAKE) $(build)=samples endif ifdef CONFIG_BUILD_DOCSRC $(Q)$(MAKE) $(build)=Documentation endif $(call vmlinux-modpost) $(call if_changed_rule,vmlinux__) $(Q)rm -f .old_version
当没有给定目标时,默认目标为all,all又被赋值为vmlinux。 CONFIG_HEADERS_CHECK、CONFIG_SAMPLES、CONFIG_BUILD_DOCSRC定义在.config文件中(一般情况下不定义),因此用的是最后一段构建规则。
vmlinux的构建放在最后面讲,这里先给出依赖的生成过程。
二. 依赖$(vmlinux-lds)
在顶层目录的Makefile中:
vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds
因为我使用的arm架构,$(SRCARCH) = arm,因此 vmlinux-lds :=arch/arm/kernel/vmlinux.lds。
内核编译链接过程是依靠vmlinux.lds文件,但是未经编译的内核源码在arch/arm/kernel/目录是不存在vmlinux.lds链接脚本的,仅有vmlinux.lds.S和与之对应的vmlinux.lds.h(一些相关的宏定义)文件;vmlinux.lds是vmlinux.lds.S经过条件编译并展开一些宏定义后生成的。
具体分析请移步:linux内核链接脚本vmlinux.lds分析(待更新)
三. 依赖$(vmlinux-init)
# 顶层目录的Makefile中 vmlinux-init := $(head-y) $(init-y)
1. $(head-y)
# arch/arm/Makefile head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
对于没有MMU的处理器,MMUEXT的值为-nommu;对于有MMU的处理器,MMUEXT的值为空。 这里我们使用后者。
2. $(init-y)
# 顶层目录的Makefile中 # Objects we will link into vmlinux / subdirs we need to visit init-y := init/ ... init-y := $(patsubst %/, %/built-in.o, $(init-y))
这里$(init-y) 进行两次赋值(Makefile中 := 的使用请移步到 :Makefile 中:= ?= += =的区别)。后一次是把init目录加了一个built-in.o。 $(vmlinux-init)最终展开为 :
vmlinux-init := arch/arm/kernel/head.o arch/arm/kernel/init_task.o init/built-in.o
四. 依赖$(vmlinux-main)
由于在顶层Makefile中包含了架构体系目录下的Makefile文件(“include $ (srctree)/arch/$(SRCARCH)/Makefile”,以arm架构为例,就是包含了/arch/arm/Makefile 文件),所以在分析这些目标和依赖时,不要忘记这个Makefile中相关的定义。具体如下:
# 顶层目录的Makefile中 include $(srctree)/arch/$(SRCARCH)/Makefile ... # Objects we will link into vmlinux / subdirs we need to visit drivers-y := drivers/ sound/ firmware/ net-y := net/ libs-y := lib/ core-y := usr/ ... core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/ ... core-y := $(patsubst %/, %/built-in.o, $(core-y)) drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y)) net-y := $(patsubst %/, %/built-in.o, $(net-y)) libs-y1 := $(patsubst %/, %/lib.a, $(libs-y)) libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y)) libs-y := $(libs-y1) $(libs-y2) ... vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y) # 在arch/arm/Makefile中 core-$(CONFIG_FPE_NWFPE) += arch/arm/nwfpe/ core-$(CONFIG_FPE_FASTFPE) += $(FASTFPE_OBJ) core-$(CONFIG_VFP) += arch/arm/vfp/ # If we have a machine-specific directory, then include it in the build. core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/ core-y += $(machdirs) $(platdirs) 机器和平台相关 drivers-$(CONFIG_OPROFILE) += arch/arm/oprofile/ libs-y := arch/arm/lib/ $(libs-y)
其中CONFIG_FPE_NWFPE、CONFIG_VFP、CONFIG_OPROFILE定义在配置文件.config中,例如mini6410开发板的配置文件定义如下:
# # At least one emulation must be selected # # CONFIG_FPE_NWFPE_XP is not set # CONFIG_FPE_FASTFPE is not set CONFIG_FPE_NWFPE=y CONFIG_VFP=y
这下,内核根目录下所有相关的子目录就都包含了。除了lib目录下有两个文件,built-in.o 和 lib.a,其余下都只有一个文件built-in.o,xxx/built-in.o的生成规则请移步到linux内核Makefile中的变量build— 过渡篇(五)
$(vmlinux-main)最终展开为 :
vmlinux-main := \ #core-y usr/built-in.o kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o \ arch/arm/nwfpe/built-in.o arch/arm/vfp/built-in.o \ arch/arm/kernel/built-in.o arch/arm/mm/built-in.o arch/arm/common/built-in.o \ arch/arm/mach-s3c64xx/built-in.o arch/arm/plat-samsung/built-in.o \ #libs-y arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o \ #drivers-y drivers/built-in.o sound/built-in.o firmware/built-in.o \ #net-y net/built-in.o
五. 依赖vmlinux.o
内核构建系统之所以要在链接 vmlinux 之前,去链接出vmlinux.o。其原因并不是要将 vmlinux.o 链接进 vmlinux,而是要在链接 vmlinux.o 的过程中做完两个动作: elf section 是否 mis-match 的检查;生成内核导出符号文件 Module.symvers(Symbol version dump文件);如果有兴趣,请移步到linux内核vmlinux的编译过程之 — vmlinux.o详解(八)
六. 依赖$(kallsyms.o)
$(kallsyms.o)里面存的主要是把内核用到的所有函数地址和名称。请移步到:内核vmlinux的编译过程之 — $(kallsyms.o)详解(待更新)
七. vmlinux目标的构建
在文章的开头已给出目标的定义和构建规则,这里只列出重要的部分:
# vmlinux image - including updated kernel symbols vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE $(call vmlinux-modpost) $(call if_changed_rule,vmlinux__) $(Q)rm -f .old_version
1. $(call vmlinux-modpost)
在我用的这个内核版本源码中,始终未找到变量 vmlinux-modpost 的定义,我觉得 $(call vmlinux-modpost) 是条多余的且没用的命令。在内核代码里,这样的多余现象也是常有的。
2. $(call if_changed_rule,vmlinux__)
命令 $(call if_changed_rule,vmlinux__) 会调用 rule_vmlinux__ 变量所定义的命令,在顶层 Makefile 中找到 rule_vmlinux__ 的定义:
#顶层Makefile # Generate System.map quiet_cmd_sysmap = SYSMAP cmd_sysmap = $(CONFIG_SHELL) $(srctree)/scripts/mksysmap # Link of vmlinux # If CONFIG_KALLSYMS is set .version is already updated # Generate System.map and verify that the content is consistent # Use + in front of the vmlinux_version rule to silent warning with make -j2 # First command is ':' to allow us to use + in front of the rule define rule_vmlinux__ : $(if $(CONFIG_KALLSYMS),,+$(call cmd,vmlinux_version)) $(call cmd,vmlinux__) $(Q)echo 'cmd_$@ := $(cmd_vmlinux__)' > $(@D)/.$(@F).cmd $(Q)$(if $($(quiet)cmd_sysmap), \ echo ' $($(quiet)cmd_sysmap) System.map' &&) \ $(cmd_sysmap) $@ System.map; \ if [ $$? -ne 0 ]; then \ rm -f $@; \ /bin/false; \ fi; $(verify_kallsyms) endef
2.1 $ (if $ (CONFIG_KALLSYMS),+$(call cmd,vmlinux_version))
构建系统会检查是否有定义过 CONFIG_KALLSYMS,如果没有定义过,它就使用 cmd_vmlinux_version 来递增链接版本。一般我们默认配置CONFIG_KALLSYMS =y。
2.2 $(call cmd,vmlinux__)
调用 cmd_vmlinux__ 去把 vmlinux 链接出来。具体代码为:
# 顶层Makefile # 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) vmlinux.o FORCE ,$^)
实际执行命令如下:
arm-linux-ld -EL -p --no-undefined -X --build-id -o vmlinux -T arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o arch/arm/kernel/init_task.o init/built-in.o --start-group usr/built-in.o arch/arm/nwfpe/built-in.o arch/arm/vfp/built-in.o arch/arm/kernel/built-in.o arch/arm/mm/built-in.o arch/arm/common/built-in.o arch/arm/mach-s3c64xx/built-in.o arch/arm/plat-samsung/built-in.o kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o drivers/built-in.o sound/built-in.o firmware/built-in.o net/built-in.o --end-group .tmp_kallsyms2.o
在这里,注意上面的链接将把 …/arch/arm/kernel/head.o 放在映像文件 vmlinux 的最前面,这是由链接器脚本所规定的,后续内核启动分栏文章中的分析会告诉你这个 head.o 正是整个 Linux 内核开始运行的地方。
2.3 $ (Q)echo ‘cmd_$@ := $(cmd_vmlinux__)’ > $(@D)/. $(@F).cmd
将编译 vmlinux的命令写入到 .vmlinux.cmd文件中保存起来,以便下次再编译内核时可以进行新旧命令的比较。
实际执行命令如下:
echo 'cmd_vmlinux := /home/hh/opt/FriendlyARM/toolschain/4.5.1/bin/arm-linux-ld -EL -p --no-undefined -X --build-id -o vmlinux -T arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o arch/arm/kernel/init_task.o init/built-in.o --start-group usr/built-in.o arch/arm/nwfpe/built-in.o arch/arm/vfp/built-in.o arch/arm/kernel/built-in.o arch/arm/mm/built-in.o arch/arm/common/built-in.o arch/arm/mach-s3c64xx/built-in.o arch/arm/plat-samsung/built-in.o kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o drivers/built-in.o sound/built-in.o firmware/built-in.o net/built-in.o --end-group .tmp_kallsyms2.o' > ./.vmlinux.cmd
2.4 $ (Q) $ (if $ ($ (quiet)cmd_sysmap), echo ’ $ ($(quiet)cmd_sysmap) System.map’ &&) $(cmd_sysmap) $@ System.map; if [ $ $? -ne 0 ]; then rm -f $@; /bin/false;fi;
判断quiet_cmd_sysmap或者cmd_sysmap变量是否有定义,如有定义,把接下来即将执行的命令打印出来,并执行。
实际打印如下:
echo ' /bin/bash /home/hh/linux-2.6.38/scripts/mksysmap System.map' && /bin/bash /home/hh/linux-2.6.38/scripts/mksysmap vmlinux System.map; if [ $? -ne 0 ]; then rm -f vmlinux; /bin/false; fi;
$(cmd_sysmap) $@ System.map
变量cmd_sysmap定义在顶层Makefile中:
# 顶层Makefile # SHELL used by kbuild CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \ else if [ -x /bin/bash ]; then echo /bin/bash; \ else echo sh; fi ; fi) ... # Generate System.map quiet_cmd_sysmap = SYSMAP cmd_sysmap = $(CONFIG_SHELL) $(srctree)/scripts/mksysmap
使用 cmd_sysmap 所定义的命令来生成基本内核符号表文件 System.map。在其中包含有所有内核符号以及它们的地址,实际上前面用 kallsyms 包含在基本内核映像中的函数/变量地址及名称信息都等同于 System.map 中的内容,我们以后对内核代码的分析过程会经常引用这个文件。
/bin/bash /home/hh/linux-2.6.38/scripts/mksysmap vmlinux System.map
2.5 $(verify_kallsyms)
最后会额外做一步,用 verify_kallsyms 来确认前面的 kallsyms 是否工作正常。确认的方法是先拿前面的 .tmp_vmlinux2 重新生成一份新的map: .tmp_System.map。接着构建系统会比较 .tmp_System.map 和 System.map,如果不一致,那说明 kallsyms 子系统代码工作不正常,所以构建系统会建议你设置 CONFIG_KALLSYMS_EXTRA_PASS 来重新make vmlinux。verify_kallsyms 的定义为:
define verify_kallsyms $(Q)$(if $($(quiet)cmd_sysmap), \ echo ' $($(quiet)cmd_sysmap) .tmp_System.map' &&) \ $(cmd_sysmap) .tmp_vmlinux$(last_kallsyms) .tmp_System.map $(Q)cmp -s System.map .tmp_System.map || \ (echo Inconsistent kallsyms data; \ echo Try setting CONFIG_KALLSYMS_EXTRA_PASS; \ rm .tmp_kallsyms* ; /bin/false ) endef
实际执行时的打印:
echo ' /bin/bash /home/hh/linux-2.6.38/scripts/mksysmap .tmp_System.map' && /bin/bash /home/hh/linux-2.6.38/scripts/mksysmap .tmp_vmlinux2 .tmp_System.map /bin/bash /home/hh/linux-2.6.38/scripts/mksysmap .tmp_System.map cmp -s System.map .tmp_System.map || (echo Inconsistent kallsyms data; echo Try setting CONFIG_KALLSYMS_EXTRA_PASS; rm .tmp_kallsyms* ; /bin/false )
3. $(Q)rm -f .old_version
rm -f .old_version
总结
linux内核中编译单个.o的执行顺序:
-------------------------------------------------------------------------------------------------- usr/built-in.o kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o #平台相关 arch/arm/nwfpe/built-in.o arch/arm/vfp/built-in.o arch/arm/kernel/built-in.o arch/arm/mm/built-in.o arch/arm/common/built-in.o arch/arm/mach-s3c64xx/built-in.o arch/arm/plat-samsung/built-in.o || sound/built-in.o arch/arm/lib/lib.a arch/arm/kernel/init_task.o || firmware/built-in.o arch/arm/lib/built-in.o arch/arm/kernel/head.o init/built-in.o || drivers/built-in.o lib/lib.a || || || || lib/built-in.o net/built-in.o || || || || || || $(head-y) $(init-y) $(core-y) $(drivers-y) $(libs-y) $(net-y) | | | | | | | | | | | / \ / \ \ / / \ / \ \ / / \ / \ \ / / \ / \ \ / / \ / \ \ / / $(vmlinux-init) .tmp_kallsyms2.o $(vmlinux-main) $(vmlinux-lds) \ | / / \ | / / \ | / / \ | / / \ | / / \ | / 链接 / —— —— —— —— —— —— —— —— —— —— —— / vmlinux ----------------------------------------------------------------------------------------------------
本文来自博客园,作者:BSP-路人甲,转载请注明原文链接:https://www.cnblogs.com/jianhua1992/p/16852799.html,并保留此段声明,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理