Linux内核学习之编译篇
2015-09-27 12:56
567 查看
Linux内核学习之编译篇
Linux内核发展至今,文件数已经超过5万,代码量相当巨大。这一方面是内核功能不断增强补充的原因,另一方面当然是Linux的兼容性考虑,导致整个工程非常浩荡,而且很多文件名都一样,处于不同目录而已,这样一来,读者在学习阅读时就容易困惑,到底那个文件才是我需要的呢?这就涉及到本篇要谈到的问题。当然本文不只是告诉你怎么找文件,更主要是谈谈编译方面。了解Makefile
Kconfig概述
Linux Makefile
了解Makefile
关于Makefile的语法,基本规则很简单,就是告诉make程序你的工程结构,你所需要编译的目标,以及目标又依赖于什么文件生成。最简单的makfile只要有这几个基本要素就够了。比如Linux 根目录下的Makefile中的一个规则如下:config: scripts_basic outputmakefile FORCE $(Q)$(MAKE) $(build)=scripts/kconfig $@
含义:config是编译目标,依赖于3个文件(或伪目标):scripts_basic outputmakefile FORCE,而接下来的一行以TAB键开始(必须如此),就是针对这个目标所要执行的命令,这个命令是Linux环境下可执行的程序或shell脚本。
更加具体的语法,我强烈推荐阅读陈皓的文章:跟我一起写Makefile
Kconfig概述
这又是什么东西?其实我很想直接就开始分析Linux Makefile的内容,但是Linux这个系统的配置比较复杂,又绕不开这个。本节主要简要介绍其作用,而具体的语法不打算详述。可以参考linux工程目录下的文档:Documentation\kbuild\kconfig-language.txt,英文偏弱的,就去度娘搜一些中文材料看看。不过本人建议结合配置界面和Kconfig对照就很清楚了。我所用的版本为4.1.6,具体的版本可以用任意ftp软件连接ftp.kernel.org获取,或者直接登录kernel网页版。
OK,我们随便打开一个Linux子目录的Makefile来大概了解一下,比如mm目录下的Makefile部分内容:
obj-$(CONFIG_SWAP) += page_io.o swap_state.o swapfile.o obj-$(CONFIG_FRONTSWAP) += frontswap.o obj-$(CONFIG_ZSWAP) += zswap.o obj-$(CONFIG_HAS_DMA) += dmapool.o obj-$(CONFIG_HUGETLBFS) += hugetlb.o
以上你会看到有很多
CONFIG_开头的宏,这个哪里来的?别急,打开同一个目录下的Kconfig文件,截取部分如下:
config FRONTSWAP bool "Enable frontswap to cache swap pages if tmem is present" depends on SWAP default n help Frontswap is so named because it can be thought of as the opposite of a "backing" store for a swap device. The data is stored into "transcendent memory", memory that is not directly accessible or addressable by the kernel and is of unknown and possibly time-varying size. When space in transcendent memory is available, a significant swap I/O reduction may be achieved. When none is available, all frontswap calls are reduced to a single pointer- compare-against-NULL resulting in a negligible performance hit and swap data is stored as normal on the matching swap device. If unsure, say Y to enable frontswap.
Now,你看到了”config FRONTSWAP“,这个配置项最终会构成”CONFIG_FRONTSWAP“,其值可以为n或y。这样就和前面提到的obj-$(CONFIG_FRONTSWAP)匹配上了,y表示obj-y指定的文件会最终编译进内核,n表示obj-n指定的文件不参与编译。看到这里,我希望你明白Kconfig里面的配置项和最终的宏的关系,也就是会自动添加上”CONFIG_“。
接下来,我们就应该要知道怎么来配置这些宏了,最一般的方式,我们通过在根目录下面执行如下命令来手动配置:
weimh1@weimh1-ubuntu:~/work/sourcecode/linux-4.1.6$ make menuconfig
这个编译目标(menuconfig)对应于根Makefile中的这条规则:
%config: scripts_basic outputmakefile FORCE $(Q)$(MAKE) $(build)=scripts/kconfig $@
上面编译命令最终又会进入scripts/kconfig 中的Makefile执行这个目标:
menuconfig: $(obj)/mconf $< $(silent) $(Kconfig)
之后,运行的结果如下图所示,请和前面的config FRONTSWAP 内容核对,就会大概明白其中一些含义:
首页面:
mm页面FRONTSWAP配置:
只是,因为kernel配置项繁多,要了解全部配置项基本是不可能,也不必要的。这种方式只是给你提供了一个入口,然后针对性修改相关项。通常,你可以基于某一个默认配置项文件进行配置,然后再通过这个menuconfig修改特定项。不同的架构有不同的默认文件,比如x86平台,可以在
arch/x86/configs找到相关文件:i386_defconfig。通过执行
make i386_defconfig即可基于这个文件生成.config文件,在此基础上可以再运行
make menuconfig来进行个别的调整。更多的细节请参考:Documentation\kbuild\kconfig.txt。 总之,无论怎么配置,最终都是为了生成.config文件,这个文件的内容如下形式:
这些宏最终将影响Makefile中参与编译的文件。正如你前面看到的obj-xxx类型的部分。
Linux Makefile
有了前面2个的基础,现在再来看Linux Makefile就相对明朗了些。本节开始,我们将开始真正进入内核的编译。当然,前面的config也是Makefile的一部分。Makefile的阅读,一般直接从头开始。内核完整的版本号,在开头即写明:
VERSION = 4 PATCHLEVEL = 1 SUBLEVEL = 6 EXTRAVERSION = NAME = Series 4800
忽略掉注释部分,继续往下看:
MAKEFLAGS += -rR --include-dir=$(CURDIR)
MAKEFLAGS是make内置的环境变量,这些参数的含义,可以通过
man make获得详细解释,以上”-rR“表示禁用内置的隐含规则和变量定义,”–include-dir” 指明嵌套脚本的搜索路径。
以下表示可以通过命令行参数
make V=1来输出完整的命令,便于跟踪。
ifeq ("$(origin V)", "command line") KBUILD_VERBOSE = $(V) endif ifndef KBUILD_VERBOSE KBUILD_VERBOSE = 0 endif ifeq ($(KBUILD_VERBOSE),1) quiet = Q = else quiet=quiet_ Q = @ endif
接下来一部分,给你提供了
make O=out的方式将编译输出的目标文件放在一个单独的目录,比如这里指定的out目录,这样可以更加清晰区分开源文件和目标文件。当然,你也可以不这么做。比如,如果不指定
O参数,
.config文件就在当前根目录生成,如果指定
O=out,那么.config文件会放在
out/.config。
ifeq ("$(origin O)", "command line") KBUILD_OUTPUT := $(O) endif ... ifneq ($(KBUILD_OUTPUT),) # Invoke a second make in the output directory, passing relevant variables # check that the output directory actually exists saved-output := $(KBUILD_OUTPUT) KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \ && /bin/pwd) $(if $(KBUILD_OUTPUT),, \ $(error failed to create output directory "$(saved-output)")) PHONY += $(MAKECMDGOALS) sub-make $(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make @: sub-make: FORCE $(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \ -f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS)) # Leave processing to above invocation of make skip-makefile := 1 endif # ifneq ($(KBUILD_OUTPUT),)
从上可以看出,当指定了
O=out时,将会进入out目录执行:
$(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \ -f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS))
继续往下看:
ifeq ("$(origin M)", "command line") KBUILD_EXTMOD := $(M) endif # If building an external module we do not care about the all: rule # but instead _all depend on modules PHONY += all ifeq ($(KBUILD_EXTMOD),) _all: all else _all: modules endif ... ... ... all: vmlinux ... ... ... vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) 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 ifdef CONFIG_GDB_SCRIPTS $(Q)ln -fsn `cd $(srctree) && /bin/pwd`/scripts/gdb/vmlinux-gdb.py endif +$(call if_changed,link-vmlinux)
这部分允许你单独编译某个模块,用
make M=dir的形式。而且可以看到,如果不是编译模块,那么默认的目标是all,否则是modules。all又依赖与vmlinux,vmlinux又依赖于vmlinux-deps等,以此一层层完成编译,最终生成vmlinux映像。
下面几个宏也需要了解一下:
ARCH ?= $(SUBARCH) CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%) ... AS = $(CROSS_COMPILE)as LD = $(CROSS_COMPILE)ld CC = $(CROSS_COMPILE)gcc CPP = $(CC) -E AR = $(CROSS_COMPILE)ar NM = $(CROSS_COMPILE)nm STRIP = $(CROSS_COMPILE)strip OBJCOPY = $(CROSS_COMPILE)objcopy OBJDUMP = $(CROSS_COMPILE)objdump ...
其中,
ARCH和
CROSS_COMPILE可以作为命令行参数传入,尤其是在编译嵌入式的Linux系统时,需要明确这2个宏。我这里仅仅举个例子,因为这个具体内容并不影响我们理解Makefile内容,不再赘述:
make ARCH=arm64 CROSS_COMPILE=/home/weimh1/work/k5_dev/prebuilts/gcc/linux-x86/aarch64/cit-aarch64-linux-android-4.9/bin/aarch64-linux-android-
下面2个宏也需要关注,看懂了这个,你就能够明白在C代码中
#include xxxx该怎么写相对路径:
USERINCLUDE := \ -I$(srctree)/arch/$(hdr-arch)/include/uapi \ -Iarch/$(hdr-arch)/include/generated/uapi \ -I$(srctree)/include/uapi \ -Iinclude/generated/uapi \ -include $(srctree)/include/linux/kconfig.h # Use LINUXINCLUDE when you must reference the include/ directory. # Needed to be compatible with the O= option LINUXINCLUDE := \ -I$(srctree)/arch/$(hdr-arch)/include \ -Iarch/$(hdr-arch)/include/generated/uapi \ -Iarch/$(hdr-arch)/include/generated \ $(if $(KBUILD_SRC), -I$(srctree)/include) \ -Iinclude \ $(USERINCLUDE)
接下来,将面对大量的目标和依赖关系,这些内容梳理清楚了,整个工程结构也就大概清楚了。
写到这里,发现如果要写得很详细,篇幅会很长,所以只能点到为止,权当入门参考。
几个重要的文件
接下来我们先转去介绍几个相关文件。[b]Kbuild.include[/b]
根Makefile中有下面这行:
include scripts/Kbuild.include
打开这个文件,里面有几个定义需要了解。
首先是
build:
### # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj= # Usage: # $(Q)$(MAKE) $(build)=dir build := -f $(srctree)/scripts/Makefile.build obj
它用在哪里?举个例子:
比如前面谈配置的时候提到的:
%config: scripts_basic outputmakefile FORCE $(Q)$(MAKE) $(build)=scripts/kconfig $@
把上面的build定义,展开后就很清晰了:
%config: scripts_basic outputmakefile FORCE $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig $@
可见,实际上build的作用是执行scripts/Makefile.build, obj指向具体的目录。于是,这里引出了第二个需要关注的文件
Makefile.build,我们留到后面再简述。
继续看Kbuild.include的另外几个定义
# echo command. # Short version is used, if $(quiet) equals `quiet_', otherwise full one. echo-cmd = $(if $($(quiet)cmd_$(1)),\ echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';) # printing commands cmd = @$(echo-cmd) $(cmd_$(1)) ... # Execute command if command has changed or prerequisite(s) are updated. # if_changed = $(if $(strip $(any-prereq) $(arg-check)), \ @set -e; \ $(echo-cmd) $(cmd_$(1)); \ printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)
请记住上面2个变量,很多地方用上,比如:
cmd_link_o_target = $(if $(strip $(obj-y)),\ $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \ $(cmd_secanalysis),\ rm -f $@; $(AR) rcs$(KBUILD_ARFLAGS) $@) $(builtin-target): $(obj-y) FORCE $(call if_changed,link_o_target)
展开后实际上最终执行了
cmd_link_o_target。其他不再赘述,自己对照原文去理解。
[b]Makefile.build[/b]
前文已经提到这个文件,这个文件主要定义了一些通用的模式匹配规则,比如:
# Built-in and composite module parts $(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE $(call cmd,force_checksrc) $(call if_changed_rule,cc_o_c)
上面这个规则定义了所有默认的c文件如何编译成.o目标文件。执行的函数为
$(call if_changed_rule,cc_o_c),而if_changed_rule
请在前文描述的Kbuild.include中查找。
其他规则类似,请自行阅读。
写到这里,如果你已经学会联系着这几个文件来阅读Makefile,我想你就已经入门了。至于要看到多深入,那纯粹是个人喜好问题,对于大多数人而言,其实大概了解也就够了。
OK,我们又回到根目录的总Makefile继续。剩下的部分,我们挑选几个来看看,不再从头到尾一个个描述。为了方便,我将几个重要的列在一块,中间省略的用
...表示。
init-y := init/ drivers-y := drivers/ sound/ firmware/ net-y := net/ libs-y := lib/ core-y := usr/ ... core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/ 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 := $(patsubst %/, %/built-in.o, $(init-y)) 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) # Externally visible symbols (used by link-vmlinux.sh) export KBUILD_VMLINUX_INIT := $(head-y) $(init-y) export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y) export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds export LDFLAGS_vmlinux # used by scripts/pacmage/Makefile export KBUILD_ALLDIRS := $(sort $(filter-out arch/%,$(vmlinux-alldirs)) arch Documentation include samples scripts tools virt) vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) # Final link of vmlinux cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) quiet_cmd_link-vmlinux = LINK $@ # Include targets which we want to # execute if the rest of the kernel build went well. vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) 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 ifdef CONFIG_GDB_SCRIPTS $(Q)ln -fsn `cd $(srctree) && /bin/pwd`/scripts/gdb/vmlinux-gdb.py endif +$(call if_changed,link-vmlinux)
以上内容主要描述了
vmlinux是如何生成的,它依赖于
vmlinux-deps,而
vmlinux-deps又依赖于
$(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)。
这三个变量又是什么?
1.
KBUILD_LDS:定义的是各个目标在最终映像文件中的布局;
2.
KBUILD_VMLINUX_INIT: 除了init-y指定的内容,还有一个此文中没有出现过的
$(head-y),这个文件和具体的架构相关,所以它在另外一个文件中定义,请看下面文件:
include arch/$(SRCARCH)/Makefile
如果你的
SRCARCH是x86,那么请打开文件
arch/x86/Makefile,你可以找到下面内容:
head-y := arch/x86/kernel/head_$(BITS).o head-y += arch/x86/kernel/head$(BITS).o head-y += arch/x86/kernel/head.o
其中,
BITS的值可以是32或者64位,取决你的机器实际配置。
head-y和init-y就构成了linux的启动和初始化代码,并被链接到最后的vmlinux中。
3.
KBUILD_VMLINUX_MAIN: (core−y)(core-y) (libs-y) (drivers−y)(drivers-y) (net-y)
这个定义的则是整个内核的核心文件。编译的时候会依次进入这些目录,编译出对应的
built-in.o并最终链接到vmlinux中。
其他的诸多内容,最终都是为上面服务所引出的一系列规则。已经不再影响大局,不再赘述。
相关文章推荐
- Linux&Windows利用CRT的小文件传输工具--rz/sz
- linux profile vs .bashrc
- Linux实验二
- Linux常用命令之文件处理命令
- 第三周linux学习
- 第三章 Linux文件系统
- Linux 调度器 BFS 简介
- Linux下挂在SD卡
- VIM+Ctags Linux源码阅读神器
- 第二章 SSH远程登录
- red hat enterprise linux 6 改为中文
- Centos 窗口最小化后,找不到在哪里还原
- 【Linux】various questions
- Centos 改变中文时出现乱码
- linux下aio异步读写详解与实例
- 【Linux高频命令专题(4)】sed
- 自定义Linux登录后的界面信息
- Linux密钥登录
- 第一章 安装CentOS 7系统
- linux上编译C