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配置:
mm页面

只是,因为kernel配置项繁多,要了解全部配置项基本是不可能,也不必要的。这种方式只是给你提供了一个入口,然后针对性修改相关项。通常,你可以基于某一个默认配置项文件进行配置,然后再通过这个menuconfig修改特定项。不同的架构有不同的默认文件,比如x86平台,可以在arch/x86/configs找到相关文件:i386_defconfig。通过执行make i386_defconfig 即可基于这个文件生成.config文件,在此基础上可以再运行make menuconfig 来进行个别的调整。更多的细节请参考:Documentation\kbuild\kconfig.txt。 总之,无论怎么配置,最终都是为了生成.config文件,这个文件的内容如下形式:

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

其中,ARCHCROSS_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 : (corey)(libs-y) (driversy)(net-y)
这个定义的则是整个内核的核心文件。编译的时候会依次进入这些目录,编译出对应的built-in.o 并最终链接到vmlinux中。

其他的诸多内容,最终都是为上面服务所引出的一系列规则。已经不再影响大局,不再赘述。


0 0
原创粉丝点击