kbuild简介
从Makefile说起:
obj-m+=hello.oall:make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modulesclean:make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean
这个 Makefile 借助于 linux 内核的 Kbuild 系统(专用于 linux 内核编译)进行编译,也就是内嵌到 Kbuild 系统进行单个模块的编译
obj-m ,该变量在内核 top Makefile 中被定义,它的值为一系列的可加载外部模块名,以 .o 为后缀。
相对应的,obj-y 为一系列的编译进内核的模块名,以.o为后缀。
可以看到,这里并没有输入 hello.c 源文件,熟悉 Makefile 的人应该知道,这得益于 Makefile 的自动推导功能,需要编译生成 filename.o 文件而没有显示地指定 filename.c 文件位置时, make 查找 filename.c 是否存在,如果存在就正常编译,如果不存在,则报错。
obj-m+=hello.o,这条语句就是显式地将 hello.o 添加到待编译外部可加载模块列表中,编译成 hello.ko ,而 hello.o 则由 make 的自动推导功能编译 hello.c 文件生成。
modules,属于[target]部分,事实上,这是个可选选项。默认行为是将源文件编译并生成内核模块,即module(s),但是它还支持以下选项:
- modules_install:安装这个外部模块,默认安装地址是/lib/modules/$(uname -r)/extra/,同时可以由内建变量 INSTALL_MOD_PATH 指定安装目录
- clean:卸载源文件目录下编译过程生成的文件,在上文的 Makefile 最后一行可以看到。
编译多个源文件:
obj-m += hello.ohello-y := a.o b.o hello_world.o #hello.o 目标文件依赖于 a.o,b.o,hello_world.oobj-m := foo.o bar.ofoo-y := <foo_srcs>bar-y := <bar_srcs>
.config
Kbuild系统做了什么
Kbuild 系统并非仅仅是完成一个常规 Makefile 的工作,除了我们以为的内核编译,它还同时完成以下的工作:
- 输出内核与模块符号
- 输出内核头文件,用户头文件
- 编译 dts 设备树文件
- 安装编译生成的文件到系统
- ….
可以说 Kbuild 系统几乎覆盖了整个内核的配置、编译、安装、系统裁剪等等。
kbuild系统中的makeifle文件
Makefile 主要是以下五个部分:
1. 顶层Makefile : 在源代码的根目录有个顶层Makefile,顶层Makefile的作用就是负责生成两个最重要的部分:编译生成vmlinux和各种模块。2. .config文件 : 这个config文件主要是产生自用户对内核模块的配置,有三种配置方式:编译进内核编译成可加载模块不进行编译。3. arch/\$(ARCH)/Makefile : 从目录可以看出,这个 Makefile 主要是根据指定的平台对内核镜像进行相应的配置,提供平台信息给顶层 Makefile。4. scirpts/Makefile. : 这些 Makefile 配置文件包含了构建内核的规则。5. kbuild Makefiles : 每一个模块都是单独被编译然后再链接的,所以这一种 kbiuld Makefile几乎在每个模块中都存在.在这些模块文件(子目录)中,也可以使用 6. Kbuild 文件代替 Makefile,当两者同时存在时,优先选择 Kbuild 文件进行编译工作,只是用户习惯性地使用 Makefile 来命名。
vmlinux
kbuild 编译所有的 obj-y 的文件,然后调用 \$(AR) rcSTP 将所有被编译的目标文件进行打包,打包成 built-in.a 文件,需要注意的是这仅仅是一份压缩版的存档,这个目标文件里面并不包含符号表,既然没有符号表,它就不能被链接。
紧接着调用scripts/link-vmlinux.sh,将上面产生的不带符号表的目标文件添加符号表和索引,作为生成vmlinux镜像的输入文件,链接生成vmlinux。
SCRIPTS/*
仅仅是靠 Makefile 的功能是很难完成整个内核的配置编译以及其他功能的,scripts/ 目录下有相当多的脚本对整个内核编译进行控制,其中列出几个非常重要的文件:
- Kbuild.include : 定义了常用的一系列通用变量与函数,在 top Makefile 开始时就被 include 包含,作用于整个内核的编译过程。
- scripts/Makefile.build : 根据用户传入的参数完成真正核心的编译工作,包括编译目标的确定、递归进入子目录的编译工作等等,作用与整个内核的编译过程。
- scripts/Makefile.lib :负责根据用户配置或者 top Makefile 传入的参数,对各类待编译文件进行分类处理,以确定最后需要被编译的文件、需要递归编译的子目录,将结果赋值给相应的变量以供真正的编译程序使用。
- scripts/link-vmlinux.sh : 对于每一个递归进入的编译目录,编译完成之后,都将在该目录下生成一个 build-in.a 文件,这个 build-in.a 文件就是由该目录下或子目录下需要编译进内核的模块打包而成,link-vmlinux.sh 将这些文件统一链接起来,生成对应的镜像。
- scripts/Makefile.host : 这个文件主要控制生成主机程序,严格来说,主机程序并不主导编译过程,它只是作为一种辅助软件,比如 menuconfig 在编译主机上的界面实现,fixdep 检查工具(检查头文件依赖)等等。
scripts/KBuild.include
在整个Kbuild系统中,scripts/Makefile.include 提供了大量通用函数以及变量的定义,这些定义将被 Makefile.build 、Makefile.lib 和 top Makefile频繁调用,以实现相应的功能,scripts/Makefile.include 参与整个内核编译的过程,是编译的核心脚本之一。$(build)
如果说重要性,这个函数的定义是当之无愧的榜首,我们先来看看它的定义:
build := -f $(srctree)/scripts/Makefile.build obj
乍一看好像也是云里雾里,我们需要将它代入到具体的调用中才能详细分析,下面举几个调用的示例:
%config: scripts_basic outputmakefile FORCE $(Q)$(MAKE) $(build)=scripts/kconfig $@ $(vmlinux-dirs): prepare $(Q)$(MAKE) $(build)=$@ need-builtin=1
其中,第一个是 make menuconfig 的规则,执行配置,生成.config。
第二个是直接执行 make,编译vmlinux的依赖vmlinux-dirs时对应的规则。
以第一个示例为例,将\$(build) 展开就是这样的:
%config: scripts_basic outputmakefile FORCE$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig $@
\$(Q)是一个打印控制参数,决定在执行该指令时是否将其打印到终端。
\$(MAKE),为理解方便,我们可以直接理解为 make。
所以,当我们执行make menuconfig时,就是对应这么一条指令:
menuconfig: scripts_basic outputmakefile FORCEmake -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig menuconfigmake -f \$(srctree)/scripts/Makefile.build: 这个指令会将 file 作为Makefile 读入并解析,相当于先执行另一个指定的文件,这个文件不一定要以 makfile 命名,在这里就是执行\$(srctree)/scripts/Makefile.build 文件。obj=scripts/kconfig:给目标makfile中 obj 变量赋值为scripts/kconfig,具体的处理在scripts/Makefile.build,事实上,在 scripts/Makefile.build 的处理中,会在 obj 所对应的目录中寻找 Kbuild 或者 Makefile 文件,并包含它menuconfig:的示例中,就是先执行scripts/kconfig/Makefile,然后执行该Makefile中指定的目标:menuconfig。
$(if_changed)
if_changed 指令也是当仁不让的核心指令,顾名思义,它的功能就是检查文件的变动。
在 Makefile 中实际的用法是这样的,
foo:$(call if_changed,$@)
一般情况下 if_changed 函数被调用时,后面都接一个参数,那么这个语句是什么作用呢?我们来看具体定义:
if_changed = $(if $(strip $(any-prereq) $(arg-check)), \$(cmd); \printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)
首先,是一个 if 函数判断,判断 \$(any-prereq) \$(arg-check) 是否都为空,如果两者有一者不为空,则执行 $(cmd),否则打印相关信息。
其中,any-prereq 表示依赖文件中被修改的文件。
arg-check,顾名思义,就是检查传入的参数,检查是否存在 cmd\$@,至于为什么是cmd\$@,我们接着看。
简单来说,调用 ifchanged 时会传入一个参数\$var,当目标的依赖文件有更新时,就执行 cmd\$var 指令。比如下面 vmlinux 编译的示例:
cmd_link-vmlinux = \$(CONFIG_SHELL) $< $(LD) $(KBUILD_LDFLAGS) $(LDFLAGS_vmlinux) ; \$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE$(call if_changed,link-vmlinux)
可以看到,vmlinux 的依赖文件列表为 scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE。
调用 if_changed 函数时传入的参数为 link-vmlinux,当依赖文件有更新时,将执行 cmd_link-vmlinux
与\$(if_changed)同类型的,还有\$(if_changed_dep),\$(if_changed_rule)。
\$(filechk)
它的功能是检查文件,严格来说是先操作文件,再检查文件的更新。:
- mkdir -p \$(dir $@):如果\$@目录不存在,就创建目录,\$@是编译规则中的目标部分。($@ 在 Makefile 的规则中表示需要生成的目标文件)
- 执行 filechk_\$(1) ,然后将执行结果保存到 \$@.tmp中
- 对比 \$@.tmp 和 $@ 是否有更新,有更新就使用 \$@.tmp,否则删除 \$@.tmp。
这部分命令的作用就是输出 kernelrelease 到 kernel.release.tmp 文件,最后对比 kernel.release 文件是否更新,kernelrelease 对应内核版本。
scripts/Makefile.lib
在linux内核的整个Kbuild系统中,Makefile.lib 对于目标编译起着决定性的作用,如果说 Makefile.build 负责执行 make 的编译过程,而 Makefile.lib 则决定了哪些文件需要编译,哪些目录需要递归进入。
编译标志位
Makefile.lib开头的部分是这样的:
asflags-y += $(EXTRA_AFLAGS)ccflags-y += $(EXTRA_CFLAGS)cppflags-y += $(EXTRA_CPPFLAGS)ldflags-y += $(EXTRA_LDFLAGS)KBUILD_AFLAGS += $(subdir-asflags-y)KBUILD_CFLAGS += $(subdir-ccflags-y)
目录及文件处理部分
去除重复部分
// 去除obj-m中已经定义在obj-y中的部分obj-m := $(filter-out $(obj-y),$(obj-m))// 去除lib-y中已经定义在obj-y中的部分lib-y := $(filter-out $(obj-y), $(sort $(lib-y) $(lib-m)))这一部分主要是去重,如果某个模块已经被定义在obj-y中,证明已经或者将要被编译进内核,就没有必要在其他地方进行编译了
modorder处理
//将obj-y中的目录 dir 修改为 dir/modules.order赋值给modorder,//将obj-m中的.o修改为.ko赋值给modorder。modorder := $(patsubst %/,%/modules.order,\$(filter %/, $(obj-y)) $(obj-m:.o=.ko))
从 modorder 的源码定义来看,将 obj-y/m 的 %/ 提取出来并修改为 %/modules.order,比如 obj-y 中的 driver/ 变量,则将其修改为 driver/modules.order 并赋值给 modorder 变量。
同时,将所有的 obj-m 中的 .o 替换成 .ko 文件并赋值给 modorder。
那么,各子目录下的 modules.order 文件有什么作用呢?
官方文档解析为:This file records the order in which modules appear in Makefiles. Thisis used by modprobe to deterministically resolve aliases that matchmultiple modules。
内核将编译的外部模块全部记录在 modules.order 文件中,以便 modprobe 命令在加载卸载时查询使用。
目录的处理
在查看各级子目录下的 Makefile 时,发现 obj-y/m 的值并非只有目标文件,还有一些目标文件夹,但是文件夹并不能被直接编译,那么它是被怎么处理的呢?带着这个疑问接着看:
//挑选出obj-y 和 obj-m 中的纯目录部分,然后添加到subdir-y和subdir-m中。__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))subdir-y += $(__subdir-y)__subdir-m := $(patsubst %/,%,$(filter %/, $(obj-m)))subdir-m += $(__subdir-m)//需要被递归搜寻的子路径,带有可编译内部和外部模块的子目录。subdir-ym := $(sort $(subdir-y) $(subdir-m))//obj-y 中纯目录部分则将其改名为dir/build-in.a,obj-y的其他部分则不变。obj-y := $(patsubst %/, %/built-in.a, $(obj-y))//将obj-m中的纯目录部分剔除掉(因为已经在上面加入到subdir-m中了)。obj-m := $(filter-out %/, $(obj-m))
在上文中提到,obj-y 和 obj-m 的定义中同时夹杂着目标文件和目标文件夹,文件夹当然是不能直接参与编译的,所以需要将文件夹提取出来。
具体来说,就是将 obj-y/m 中以”/“结尾的纯目录部分提取出来,并赋值给 subdir-ym。
最后,在处理完后,需要删除这一部分,对于obj-y的目录而言,将其修改成 dir/build-in.a(其实就是删除 obj-y 中目录再添加一个 build-in.a 的目标文件),而对于obj-m中的目录,则是直接删除。
scripts/Makefile.build
Makefile.build入口
scripts_basic:$(Q)$(MAKE) $(build)=scripts/basic$(Q)rm -f .tmp_quiet_recordmcount
在 scripts/Makefile.build 中包含了 scripts/Kbuild.include 文件,紧接着在 scripts/Kbuild.include 文件下找到 build 的相应的定义:build := -f $(srctree)/scripts/Makefile.build obj
所以,上述的命令部分 \$(Q)\$(MAKE) \$(build)=scripts/basic 展开过程就是这样:$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=scripts/basic
$(build)对目标文件夹的处理
1 src := $(obj)23 //尝试包含 include/config/auto.conf4 -include include/config/auto.conf56 //该文件中包含大量通用函数和变量的实现7 include scripts/Kbuild.include89 //如果obj是以/目录开始的目录,kbuild-dir=boj,否则kbuild-dir = srctree/obj10 kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))11 //如果kbuild-dir存在Kbuild,则kbuild-file为Kbuild,否则为Makefile12 kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)1314 include $(kbuild-file)1516 include scripts/Makefile.lib
9-14行,处理通过 \$(build)=scripts/basic 传入的 obj = scripts/basic目录,获取该目录的相对位置.
如果该目录下存在Kbuild文件,则包含该 Kbuild (在Kbuild系统中,make优先处理 Kbuild 而不是 Makefile )文件,否则查看是否存在 Makefile文件,如果存在Makefile,就包含Makefile文件,如果两者都没有,就是一条空包含指令。
这一部分指令的意义在于:\$(build)= scripts/basic 相当于 make -f \$(srctree)/scripts/Makefile.build obj=scripts/basic,如果 obj=scripts/basic 下存在 Makefile 或者 Kbuild 文件,就默认以该文件的第一个有效目标作为编译目标。
如果指定目标 Makfile 下不存在有效目标(很可能对应 Makefile 中只是定义了需要编译的文件),就默认以\$(srctree)/scripts/Makefile.build 下的第一个有效目标作为编译目标,即__build 目标。(makefile的编译规则:以目标处理文件中第一个有效目标作为编译目标)
同样的,遵循make的规则,用户也可以指定编译目标,比如:scripts_basic: $(Q)$(MAKE) $(build)=scripts/basic $@
这样,该规则展开就变成了:scripts_basic: $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=scripts/basic scripts_basic
表示指定编译scripts/basic/Makefile 或 scripts/basic/Kbuild文件下的scripts_basic目标(这里仅为讲解方便,实际上scripts/basic/Makefile中并没有scripts_basic目标)。
接下来的内容就是包含include scripts/Makefile.lib,lib-y,lib-m等决定编译文件对象的变量就是在该文件中进行处理。
接下来就是代表编译目标文件的处理部分:
//主机程序,在前期的准备过程中可能需要用到,比如make menuconfig时需要准备命令行的图形配置。1 ifneq ($(hostprogs-y)$(hostprogs-m)$(hostlibs-y)$(hostlibs-m)$(hostcxxlibs-y)$(hostcxxlibs-m),)2 include scripts/Makefile.host3 endif//判断obj,如果obj没有指定则给出警告45 ifndef obj6 $(warning kbuild: Makefile.build is included improperly)7 endif//如果有编译库的需求,则给lib-target赋值,并将 $(obj)/lib-ksyms.o 追加到 real-obj-y 中。89 ifneq ($(strip $(lib-y) $(lib-m) $(lib-)),)10 lib-target := $(obj)/lib.a11 real-obj-y += $(obj)/lib-ksyms.o12 endif//如果需要编译 将要编译进内核(也就是obj-y指定的文件) 的模块,则赋值 builtin-target1314 ifneq ($(strip $(real-obj-y) $(need-builtin)),)15 builtin-target := $(obj)/built-in.a16 endif// 如果定义了 CONFIG_MODULES,则赋值 modorder-target。17 ifdef CONFIG_MODULES18 modorder-target := $(obj)/modules.order19 endif
同样的,对于每一部分博主都添加了一部分注释,需要特别解释的有两点:
13-16行部分,事实上,如果我们进入到每个子目录下查看Makefile文件,就会发现:obj-m 和 obj-y的赋值对象并不一定是xxx.o这种目标文件, 也可能是目录,real-obj-y可以理解为解析完目录的真正需要编译的目标文件。 need-builtin变量则是在需要编译内核模块时被赋值为1,
根据15行的 builtin-target := $(obj)/built-in.a可以看出,对于目录(值得注意的是,并非所有目录)的编译,都是先将该目录以及子目录下的所有编译进内核的目标文件打包成 built-in.a,在父目录下将该build-in.a打包进父目录的build-in.a,一层一层地往上传递。17-19行部分,CONFIG_MODULES是在.config中被定义的,这个变量被定义的条件是在make menuconfig 时使能了 “Enable loadable module support” 选项,这个选项表示内核是否支持外部模块的加载,一般情况下,这个选项都会被使能。所以,modorder-target将被赋值为$(obj)/modules.order,modules.order文件内容如下:
kernel/fs/efivarfs/efivarfs.kokernel/drivers/thermal/intel/x86_pkg_temp_thermal.kokernel/net/netfilter/nf_log_common.kokernel/net/netfilter/xt_mark.kokernel/net/netfilter/xt_nat.kokernel/net/netfilter/xt_LOG.ko
module.order这个文件记录了可加载模块在Makefile中出现的顺序,主要是提供给modprobe程序在匹配时使用。
与之对应的还有module.builtin文件和modules.builtin.modinfo。
顾名思义,module.builtin记录了被编译进内核的模块。而modules.builtin.modinfo则记录了所有被编译进内核的模块信息,作用跟modinfo命令相仿,每个信息部分都以模块名开头,毕竟所有模块写在一起是需要做区分的。
这三个文件都是提供给modprobe命令使用,modporbe根据这些信息,完成程序的加载卸载以及其他操作。
__build 默认目标
大多数情况下,都是执行了Makefile.build 中的 __build 默认目标进行编译。所以,当我们编译某个目录下的源代码,例如 drivers/ 时,主要的步骤大概是这样的:
1. 由top Makefile 调用 \$(build) 指令,此时,obj = drivers/2. 在 Makefile.build 中包含 drivers/Makefile,drivers/Makefile 中的内容为:添加需要编译的文件和需要递归进入的目录,赋值给obj-y/m,并没有定义编译目标,所以默认以 Makefile.build 中 __build 作为编译目标。3. Makefile.build 包含 Makefile.lib ,Makefile.lib 对 drivers/Makefile 中赋值的 obj-y/m 进行处理,确定 real-obj-y/m subdir-ym 的值。4. 回到 Makefile.build ,执行 __build 目标,编译 real-obj-y/m 等一众目标文件5. 执行 $(subdir-ym) 的编译,递归地进入 subdir-y/m 进行编译。6. 最后,直到没有目标可以递归进入,在递归返回的时候生成 built-in.a 文件,每一个目录下的 built-in.a 静态库都包含了该目录下所有子目录中的 built-in.a 和 *.o 文件 ,所以,在最后统一链接的时候,vmlinux.o 只需要链接源码根目录下的几个主要目录下的 built-in.a 即可,比如 drivers, init.
Kconfig
每个目录下都存在 Makefile 和 Kconfig 文件, Kconfig 负责该模块下的配置工作,Makefile 负责该模块下的编译工作。
通常情况下,子目录下的 Makefile 并不负责编译工作,只是提供当前目录下需要编译的目标文件或者需要递归进入的目录(arch目录除外),交由 scripts/Makefile.build 和 scripts/Makefile.lib 统一处理。
kbuild系统生成文件
在Makefile的编译过程中,将生成各类中间文件,通常情况下,大部分生成的中间文件是可以不用关心的,只需要关注最后生成的 vmlinux,System.map, 系统dtb以及各类外部模块等启动常用文件即可。
但是,如果要真正了解内核编译背后的原理,了解这些文件的作用是非常有必要的。下面列出一些值得关注的生成文件:
1. System.map : 该文件相当于镜像文件的符号表,记录了内核镜像中所有的符号地址,文件中对应的函数地址对应了程序运行时函数真实的地址,在调试的时候是非常有用的。
2. */built-in.a : Kbuild系统会根据配置递归地进入到子目录下进行编译工作,最后将所有目标文件链接生成一个总的 vmlinux.o ,背后的实现机制是这样的: 对于某一个需要进入编译的目录,将在该目录下生成一个 built-in.a 文件,该 built-in.a 由本目录中所有目标文件以及一级子目录下的 built-in.a 文件使用 ar 指令打包而成。其二级子目录下的所有目标文件以及该目录下的 built-in.a 将被打包进一级子目录的 built-in.a 文件中,依次递归。到根目录下时,只需要将所有一级子目录中的 built-in.a 文件链接到一起即可。
3. .*.o.d 和 .*.o.cmd : 不知道你有没有疑惑过内核 Kbuild 系统是如何处理头文件依赖问题的,它处理头文件依赖就是通过这两种文件来记录所有的依赖头文件,对于依赖的目标文件,自然是在编译规则中指定。
4. modules.order 、 modules.build 和 modules.builtin.modinfo : 这两个文件主要负责记录编译的模块,modules.builtin.modinfo记录模块信息,以供 modprobe 使用。
5. arch/$(ARCH)/boot : 通常在嵌入式的开发中,这个目录下的文件就是开发板对应的启动文件,会因平台的不同而有差异,一般包含这几部分:镜像、内核符号表、系统dtb。
6. .config :记录用户对内核模块的配置。
include/generate/* : 内核编译过程将会生成一些头文件,其中比较重要的是 autoconf.h,这是 .config 的头文件版本,以及uapi/目录下的文件,这个目录下的保存着用户头文件。
7. include/config/* : 为了解决 autoconf.h 牵一发而动全身的问题(即修改一个配置导致所有依赖 autoconf.h 的文件需要重新编译),将 autoconf.h 分散为多个头文件放在 include/config/ 下,以解决 autoconf.h 的依赖问题。
通用模板
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KDIR ?= /lib/modules/`uname -r`/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
endif
这得从 linux 内核模块 make 执行的过程说起:当键入 make 时,make 在当前目录下寻找 Makefile 并执行,KERNELRELEASE 在顶层的 Makefile 中被定义,所以在执行当前 Makefile 时KERNELRELEASE 并没有被定义,走 else 分支,直接执行
$(MAKE) -C $(KDIR) M=$(PWD) modules
而这条指令会进入到 $(KDIR) 目录,也就是源码所在根目录,调用顶层的 Makefile,在顶层 Makefile 中定义了 KERNELRELEASE 变量.
头文件包含:
ccflags-y := -I$(DIR)/include
Kbuild系统框架
内核基本配置
make V=1
V 的全拼为 verbose,表示详细的,即打印更多的信息,在编译时不指定V时,默认 V=0 ,表示不输出编译。
值得注意的是,V=1 和 V=2 并不是递进的关系,V=1时,它会打印更多更详细的信息,通常是打印出所有执行的指令,当V=2时,它将给出重新编译一个目标的理由。而不是我们自以为的 V=2 比 V=1 打印更多信息。 $(Q)$(MAKE) … 这个 \$(Q) 就是根据 V 的值来确定的,当 V=0 时,\$(Q)为空,当 V=1 时,\$(Q)为@。@,表示执行命令的同时打印该命令到终端。
通过命令行传入 M=DIR 进行编译,这种情况下在编译内核可加载模块是最常见的,不知道你还记不记得我们在编译可加载模块时 Makefile 中的这一条语句: make -C /lib/modules/\$(shell uname -r)/build/ M=\$(PWD) modules
make mrproper
make O=/labs/linux-lab/build/x86_64/linux-v3 -C /labs/linux-lab/src/linux-stable ARCH=x86 LOADADDR= CROSS_COMPILE= V= CONFIG_INITRAMFS_SOURCE= -j8 x86_64_defconfig
make O=/labs/linux-lab/build/x86_64/linux-v3 -C /labs/linux-lab/src/linux-stable ARCH=x86 LOADADDR= CROSS_COMPILE= V= CONFIG_INITRAMFS_SOURCE= -j8 bzImage
<a name="cb07a"></a>
### 编译选项
<a name="oEs8A"></a>
#### ccflags-y asflags-y和ldflags-y
编译、汇编、链接时的参数<br />这三个变量只对有定义的Makefile中使用,简而言之,这些flag在Makefile树中不会有继承效果,Makefile之间相互独立。
<a name="fP0a7"></a>
#### subdir-ccflags-y, subdir-asflags-y
本目录和所有的子目录都有效。
```bash
ccflags-y := -Werror
ccflags-y += -Idrivers/media/dvb/frontends
CFLAGS或者AFLAGS前缀
CFLAGS_foo.o = -DAUTOCONF
在编译foo.o时,添加了-DAUTOCONF编译选项
kbuild中的变量
KERNELRELEASE
这是一个字符串,用于构建安装目录的名字(一般使用版本号来区分)或者显示当前的版本号。
ARCH
定义当前的目标架构平台,比如:”X86”,”ARM”,默认情况下,ARCH的值为当前编译的主机架构,但是在交叉编译环境中,需要在顶层Makefile或者是命令行中指定架构:
make ARCH=arm …
INSTALL_PATH
指定安装目录,安装目录主要是为了放置需要安装的镜像和map(符号表)文件,系统的启动需要这些文件的参与。
INSTALL_MOD_PATH, MODLIB
INSTALL_MOD_PATH:为模块指定安装的前缀目录,这个变量在顶层Makefile中并没有被定义,用户可以使用,MODLIB为模块指定安装目录.
默认情况下,模块会被安装到$(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)中,默认INSTALL_MOD_PATH不会被指定,所以会被安装到/lib/modules/$(KERNELRELEASE)中。
INSTALL_MOD_STRIP
如果这个变量被指定,模块就会将一些额外的、运行时非必要的信息剥离出来以缩减模块的大小,当INSTALL_MOD_STRIP为1时,—strip-debug选项就会被使用,模块的调试信息将被删除,否则就执行默认的参数,模块编译时会添加一些辅助信息。
这些全局变量一旦在顶层Makefile中被定义就全局有效,但是有一点需要注意,在驱动开发时,一般编译单一的模块,执行make调用的是当前目录下的Makefile.
在这种情况下这些变量是没有被定义的,只有先调用了顶层Makefile之后,这些变量在子目录中的Makefile才被赋值。
生成header文件
vmlinux中打包了所有模块编译生成的目标文件,在驱动开发者眼中,在内核启动完成之后,它的作用相当于一个动态库,既然是一个库,如果其他开发者需要使用里面的接口,就需要相应的头文件。
自然地,build也会生成相应的header文件供开发者使用,一个最简单的方式就是用下面这个指令:
make headers_install ARCH=arm INSTALL_HDR_PATH=/DIR
ARCH:指定CPU的体系架构,默认是当前主机的架构,可以使用以下命令查看当前源码支持哪些架构:
ls -d include/asm-* | sed 's/.*-//'
INSTALL_HDR_PATH:指定头文件的放置目录,默认是./usr。
kbuild的执行过程做一下梳理
1. 根据用户(内核)的配置生成相应的.config文件
2. 将内核的版本号存入include/linux/version.h
3. 建立指向 include/asm-$(ARCH) 的符号链接,选定平台, 更新所有编译所需的文件。
4. 从顶层Makefile开始,递归地访问各个子目录,对相应的模块编译生成目标文件链接过程
5.在源代码的顶层目录链接生成vmlinux
6. 根据具体架构提供的信息添加相应符号,生成最终的启动镜像,往往不同架构之间的启动方式不一致。
这一部分包含启动指令
准备initrd镜像等平台相关的部分。
交叉编译
make ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE_DIR)/arm-linux-eabi-
第三方模块编译进内核
将需要编译的模块文件(夹)放到内核源码目录中,并配置Kconfig文件,使得在使用 make menuconfig 进行配置的时候可以对该模块进行配置。
使用make menuconfig指令,将需要编译的内核模块选成Y。
具体的步骤是这样的:
1. 将驱动源文件放在内核对应目录中,一般的驱动文件放在 drivers 目录下,字符设备放在 drivers/char 中,块设备就放在 drivers/blok 中,文件的位置遵循这个规律,如果是单个的字符设备源文件,就直接放在 drivers/char 目录下,如果内容较多足以构成一个模块,则新建一个文件夹。
2. 如果是新建文件夹,因为是分布式编译,需要在文件夹下添加一个 Makefile 文件和 Kconfig 文件并修改成指定格式,如果是单个文件直接添加,并修改当前目录下的 Makefile 和 Kconfig 文件将其添加进去即可。
3. 如果是新建文件夹,需要修改上级目录的 Makefile 和 Kconfig,以将文件夹添加到整个源码编译树中。
4. 执行make menuconfig,如果文件(夹)放置位置和Kconfig配置没有出错,将在make menuconfig的配置界面中看到对应的配置项,选中,然后执行make
