一. 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
----------------------------------------------------------------------------------------------------

linux内核vmlinux的编译过程(七)的更多相关文章

  1. Xilinx-Zynq Linux内核源码编译过程

    本文内容依据http://www.wiki.xilinx.com网址编写,编译所用操作系统为ubuntu 14 1.交叉编译环境的安装配置 1)http://www.wiki.xilinx.com/I ...

  2. 结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程

    结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程 目录 结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程 一. 实验准备 二. 实验过程 I 分析中断上下文的切换 ...

  3. Linux 内核配置和编译

    Linux 内核配置和编译 一.配置内核 (1). 为什么要配置内核 1. 硬件需求 2. 软件需求 选出需要的,去掉不要的 (2). 如何配置内核 1. make  config 基于文本模式的交互 ...

  4. Linux C程序的编译过程

    Linux C程序的编译过程 学习一门语言程序,本人觉得还是得学习它的编译规则,现在,通过小例子小结下自己对C编译的认识. /*test.c     了解C程序的编译*/ #include <s ...

  5. Linux内核配置、编译及Makefile简述

    Hi,大家好!我是CrazyCatJack.最近在学习Linux内核的配置.编译及Makefile文件.今天总结一下学习成果,分享给大家^_^ 1.解压缩打补丁 首先是解压缩你获取到的Linux内核. ...

  6. 《Linux内核分析》课程第七周学习总结

    姓名:何伟钦 学号:20135223 ( *原创作品转载请注明出处*) ( 学习课程:<Linux内核分析>MOOC课程http://mooc.study.163.com/course/U ...

  7. linux内核裁剪及编译可加载模块

    一:linux内核裁剪: 1:编译内核源码: 今天的重点内容是内核驱动的编写,在编写驱动之前首先的了解linux内核源码,linux主要是由五个子系统组成:进程调度,内存管理,文件系统,网络接口以及进 ...

  8. 20135316王剑桥Linux内核学习记笔记第七周

    20135316王剑桥<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC 1000029000 一.可执行程序是怎么得来的? 编译 ...

  9. Linux内核-模块编译和安装

    我安装Ubuntu的时候是没有安装源码的,在没有安装源码前 /usr/src/ 目录下是只有两个包含内核的头文件的文件夹的: 我的内核版本是: 所以接下来就是先安装内核源码: 执行后,/usr/src ...

  10. linux内核系列(一)编译安装Linux内核 2.6.18

    1.配置环境 操作系统:CentOS 5.2 下载linux-2.6.18版本的内核,网址:http://www.kernel.org 说明:该编译文档适合2.6.18以上的Linux内核版本,只需所 ...

随机推荐

  1. ArcGIS Pro发布地图服务(影像、矢量)

    做GIS一般都是用ArcMap发布影像或者矢量服务,由于ArcGIS后续不在更新ArcMap,改用ArcGIS Pro,本文对ArcGIS Pro发布服务进行说明. 本文示例使用(因为portal的授 ...

  2. C#中使用CAS实现无锁算法

    CAS 的基本概念 CAS(Compare-and-Swap)是一种多线程并发编程中常用的原子操作,用于实现多线程间的同步和互斥访问. 它操作通常包含三个参数:一个内存地址(通常是一个共享变量的地址) ...

  3. nginx启动报错80端口号已占用

    开启或重启Nginx时报如下错误: Nginx [emerg]: bind() to 0.0.0.0:80 failed (98: Address already in use) 原因是端口号80被其 ...

  4. Vue实战案例

    Vue项目案例 结合之前学习的 vue.js.脚手架.vuex.vue-router.axios.elementui 等知识点,来开发前端项目案例(仅前端不含后端). 1.项目搭建 其实就是将我们项目 ...

  5. SpringBoot 集成 SpringSecurity + MySQL + JWT 附源码,废话不多直接盘

    SpringBoot 集成 SpringSecurity + MySQL + JWT 无太多理论,直接盘 一般用于Web管理系统 可以先看 SpringBoot SpringSecurity 基于内存 ...

  6. 推荐一个.Ner Core开发的配置中心开源项目

    当你把单体应用改造为微服务架构,相应的配置文件,也会被分割,被分散到各个节点.这个时候就会产生一个问题,配置信息是分散的.冗余的,变成不好维护管理.这个时候我们就需要把配置信息独立出来,成立一个配置中 ...

  7. 2021-01-08:cpu和gpu有什么区别?

    福哥答案2021-01-08:[答案来自此链接:](https://www.cnblogs.com/biglucky/p/4223565.html)Cache, local memory: CPU & ...

  8. 2021-01-05:mysql的自增id的实现逻辑是什么样子的?

    福哥答案2021-01-05:答案来自这个链接:[ 每日一面 - mysql 的自增 id 的实现逻辑是什么样子的?](https://zhanghaoxin.blog.csdn.net/articl ...

  9. 2021-07-28:最短的桥。在给定的二维二进制数组 A 中,存在两座岛。(岛是由四面相连的 1 形成的一个最大组。)现在,我们可以将 0 变为 1,以使两座岛连接起来,变成一座岛。返回必须翻转的

    2021-07-28:最短的桥.在给定的二维二进制数组 A 中,存在两座岛.(岛是由四面相连的 1 形成的一个最大组.)现在,我们可以将 0 变为 1,以使两座岛连接起来,变成一座岛.返回必须翻转的 ...

  10. K8s Pod状态与容器探针

    1.pod的调度流程及常见状态 1.1.pod的调度流程 Pod创建过程如上图所示,首先用户向apiserver发送创建pod的请求,apiserver收到用于创建pod请求后,对应会对该用户身份信息 ...