Linux内核学习之编译篇
来源:互联网 发布:linux 安装 net snmp 编辑:程序博客网 时间:2024/05/17 04:29
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.oobj-$(CONFIG_FRONTSWAP) += frontswap.oobj-$(CONFIG_ZSWAP) += zswap.oobj-$(CONFIG_HAS_DMA) += dmapool.oobj-$(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 = 4PATCHLEVEL = 1SUBLEVEL = 6EXTRAVERSION =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)endififndef KBUILD_VERBOSE KBUILD_VERBOSE = 0endififeq ($(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 existssaved-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 makeskip-makefile := 1endif # 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 modulesPHONY += allifeq ($(KBUILD_EXTMOD),)_all: allelse_all: modulesendif.........all: vmlinux.........vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCEifdef CONFIG_HEADERS_CHECK $(Q)$(MAKE) -f $(srctree)/Makefile headers_checkendififdef CONFIG_SAMPLES $(Q)$(MAKE) $(build)=samplesendififdef CONFIG_BUILD_DOCSRC $(Q)$(MAKE) $(build)=Documentationendififdef CONFIG_GDB_SCRIPTS $(Q)ln -fsn `cd $(srctree) && /bin/pwd`/scripts/gdb/vmlinux-gdb.pyendif +$(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)asLD = $(CROSS_COMPILE)ldCC = $(CROSS_COMPILE)gccCPP = $(CC) -EAR = $(CROSS_COMPILE)arNM = $(CROSS_COMPILE)nmSTRIP = $(CROSS_COMPILE)stripOBJCOPY = $(CROSS_COMPILE)objcopyOBJDUMP = $(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= optionLINUXINCLUDE := \ -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)
接下来,将面对大量的目标和依赖关系,这些内容梳理清楚了,整个工程结构也就大概清楚了。
写到这里,发现如果要写得很详细,篇幅会很长,所以只能点到为止,权当入门参考。
几个重要的文件
接下来我们先转去介绍几个相关文件。
Kbuild.include
根Makefile中有下面这行: include scripts/Kbuild.include
打开这个文件,里面有几个定义需要了解。
首先是build
:
#### Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=# Usage:# $(Q)$(MAKE) $(build)=dirbuild := -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 commandscmd = @$(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
。其他不再赘述,自己对照原文去理解。
Makefile.build
前文已经提到这个文件,这个文件主要定义了一些通用的模式匹配规则,比如:
# 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.ldsexport LDFLAGS_vmlinux# used by scripts/pacmage/Makefileexport 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) FORCEifdef CONFIG_HEADERS_CHECK $(Q)$(MAKE) -f $(srctree)/Makefile headers_checkendififdef CONFIG_SAMPLES $(Q)$(MAKE) $(build)=samplesendififdef CONFIG_BUILD_DOCSRC $(Q)$(MAKE) $(build)=Documentationendififdef CONFIG_GDB_SCRIPTS $(Q)ln -fsn `cd $(srctree) && /bin/pwd`/scripts/gdb/vmlinux-gdb.pyendif +$(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).ohead-y += arch/x86/kernel/head$(BITS).ohead-y += arch/x86/kernel/head.o
其中,BITS
的值可以是32或者64位,取决你的机器实际配置。
head-y和init-y就构成了linux的启动和初始化代码,并被链接到最后的vmlinux中。
3.KBUILD_VMLINUX_MAIN
:
这个定义的则是整个内核的核心文件。编译的时候会依次进入这些目录,编译出对应的built-in.o
并最终链接到vmlinux中。
其他的诸多内容,最终都是为上面服务所引出的一系列规则。已经不再影响大局,不再赘述。
- Linux内核学习之编译篇
- linux内核学习之二:编译内核
- Linux内核源码学习之 内核编译
- linux内核学习之编译命令
- 轻松学习Linux之内核编译
- linux学习-编译内核
- Linux内核编译学习
- Linux学习:Linux内核编译
- Linux内核学习 编译Linux内核笔记
- Linux内核学习 编译Linux内核笔记
- linux 学习- 编程基础之内核配置与编译
- linux 内核驱动学习之scull代码编译
- Android学习【Android内核编译之Linux通信环境搭建】
- linux驱动学习--第六天:第四章 Linux 内核模块 之 Linux 内核模块编译
- linux内核编译学习笔记
- Linux学习记录--内核|内核模块编译
- linux内核编译之旅
- linux内核编译之旅
- 测试11班中秋晚会圆满结束
- Android浏览器控件WebView简单小实例
- leet006 ZigZag Conversion
- 分层分离的总线驱动模型分析
- WIFI网卡使用分析
- Linux内核学习之编译篇
- 下列的式子成立,采用的是几进制表示的
- 日常笔记 oracel简单函数
- 输入子系统分析
- oracle减小数据库表空间
- 浏览器找不到链接解决方案
- Linux那些事读书笔记
- mysql 加锁 解锁 页表行锁
- 条款43:学习处理模板化基类内的名称