Kbuild文件
3 Kbuild文件
大部分内核中的Makefile都是使用Kbuild组织结构的Kbuild Makefile。这章将介绍Kbuild Makefile的语法。
对于Kbuild文件名来讲,Kbuild编译系统更倾向于使用"Makefile"这个名字,当然"Kbuild"也是可以用的。但如果"Makefile"和"Kbuild"同时存在的话,Kbuild编译系统使用的将会是"Kbuild"文件。
3.1节将是对目标定义的一个快速介绍,以后的几章会提供更详细的内容以及实例。
3.1 目标定义
目标定义是Kbuild Makefile的主要部分,也是核心部分。主要是定义了要编译的文件,所有的选项,以及到哪些子目录去执行递归操作。
最简单的Kbuild makefile 只包含一行:
例子:
obj-y += foo.o
该例子告诉Kbuild:在这目录里,有一个名为foo.o的目标文件。foo.o将从foo.c或foo.S文件编译得到。
如果foo.o要编译成一模块,那就要用obj-m了。所采用的形式如下:
例子:
obj-$(CONFIG_FOO) += foo.o
$(CONFIG_FOO)可以为y(编译进内核) 或m(编译成模块)。如果CONFIG_FOO既不是y又不是m,那么该文件就不会被编译链接了。
3.2 编译进内核 -- obj-y
Kbuild Makefile 规定所有编译进内核的目标文件都在$(obj-y)列表中。而这些列表依赖内核的配置(.config)。
Kbuild编译所有的$(obj-y)文件。然后,调用"$(LD) -r"将它们合并到一个build-in.o文件中。稍后,该build-in.o会被其父Makefile链接进vmlinux中。
$(obj-y)列表中的文件是有顺序讲究的。而且$(obj-y)列表中可以有重复项:但是当第一个文件被链接到built-in.o中后,其余重复的文件就被忽略了。
链接目标这个动作也是有顺序的,因为有些函数,例如module_init()/__initcall,将会在启动时按照他们出现的顺序被调用。所以,记住改变链接的顺序可能改变你SCSI控制器的检测顺序,从而导致你的硬盘数据损害。
例子:
# drivers/isdn/i4l/Makefile
# Makefile for the kernel ISDN subsystem and device drivers.
# Each configuration option enables a list of files.
obj-$(CONFIG_ISDN) += isdn.o
obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o
3.3 编译可装载模块 -- obj-m
$(obj-m) 列举出了哪些文件要编译成可装载模块。
一个模块可以由一个文件或多个文件编译而成。如果是一个源文件,Kbuild Makefile只需简单的将其加到$(obj-m)中去就可以了。
例子:
# drivers/isdn/i4l/Makefile
obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o
注意:此例中 $(CONFIG_ISDN_PPP_BSDCOMP) 的值为'm'
如果内核模块是由多个源文件编译而成,那你就要采用上面那个例子一样的方法去声明你所要编译的模块。
Kbuild系统需要知道你所编译的模块是基于哪些文件,所以你需要通过变量:
$(<module_name>-objs)
来告诉它。
例子:
#drivers/isdn/i4l/Makefile
obj-$(CONFIG_ISDN) += isdn.o
isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o
在这个例子中,模块名是isdn.o,Kbuild将对$(isdn-objs)中列出的所有文件进行编译,然后使用"$(LD) -r"生成isdn.o。
Kbuild recognises objects used for composite objects by the suffix -objs, and the suffix -y。
This allows the Makefiles to use the value of a CONFIG_ symbol to determine if an object is part
of a composite object.
例子:
# fs/ext2/Makefile
obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y := balloc.o bitmap.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o
在这个例子中,如果 $(CONFIG_EXT2_FS_XATTR) 是 'y',xattr.o将是复合对象 ext2.o的一部分。
注意:其实,将对象编译进内核时,上面的语法同样适用。所以,如果你的 CONFIG_EXT2_FS=y,那Kbuild会按你所期望的那样,生成一个独立ext2.o文件,然后将其链接到 built-in.o中。
3.4 输出的符号
Makefile对模块输出的符号没有特殊要求。
3.5 目标库文件 -- lib-y
obj-* 中所列出的目标文件是用来编译成模块或者是链接到特定目录中的built-in.o的。同样,你也可以列出一些目标文件,它们将被包含在lib.a库中。
lib-y 所列出的目标文件将会被用来组成该目录下的一个单独的库文件。
obj-y 与 lib-y 同时列出的目标文件,因为都是可以访问的,所以该文件是不会被包含在库文件中的。
同样,lib-m 中所列出的目标文件就要包含在 lib.a 库文件中。
注意:一个Kbuild makefile可以同时列出要编译进内核的文件和要编译成库的文件。所以,在一个目录里可以同时存在 built-in.o 与 lib.a 两种文件。
例子:
# arch/i386/lib/Makefile
lib-y := chechsum.o delay.o
这将由 checksum.o 和delay.o 两个文件创建一个库文件lib.a。为了让Kbuild 系统真正认识到这里要有一个库文件 lib.a 要创建,库文件lib.a所在的目录需要加到 libs-y 列表中。
还可参考"6.3 递归向下时要访问的目录列表"。
lib-y 的使用一般限制在 lib/ 和 arch/*/lib 中。
3.6 递归向下访问目录
其实,一个Makefile只负责编译其所在目录下的对象文件。在子目录中的文件的编译要由其所在的子目录的Makefile来管理。只要你让Kbuild系统知道它应该递归操作,那么Kbuild系统就会在其子目录中自动调用 make 从而进行递归操作。
obj-y 和 obj-m 就有这样的作用。
ext2 被放在一个单独的目录下,在fs目录下的Makefile会告诉Kbuild系统使用下面的赋值进行向下递归操作。
例子:
# fs/Makefile
obj-$(CONFIG_EXT2_FS) += ext2/
如果 CONFIG_EXT2_FS 被设置为 'y'(编译进内核)或是'm'(编译成模块),相应的 obj- 变量就会被设置,并且Kbuild就会递归向下访问 ext2 目录。
Kbuild系统只是用这些信息来决定它是否需要访问该目录,而具体怎么编译由该子目录中的Makefile来决定。
It is good practice to use a CONFIG_ variable when assigning directory names. This allows kbuild to totally skip the directory if the corresponding CONFIG_ option is neither 'y' nor 'm'.
3.7 编辑标志
EXTRA_CFLAGS, EXTRA_AFLAGS, EXTRA_LDFLAGS, EXTRA_ARFLAGS
所有的EXTRA_ 变量只在其被定义的Kbuild Makefile文件中起作用。而且,EXTRA_ 变量可以在Kbuild Makefile中的所有命令中使用。
l $(EXTRA_CFLAGS) 是用 $(CC) 编译C源文件时要用到的选项。
例子:
# drivers/sound/emu10kl/Makefile
EXTRA_CFLAGS += -I$(obj)
ifdef DEBUG
EXTRA_CFLAGS += -DEMU10KL_DEBUG
endif
EXTRA_CFLAGS变量是必须的,因为顶层Makefile使用 $(EXTRA_CFLAGS)作为整个源代码树的编译选项。
l $(EXTRA_AFLAGS) 也是一个针对每个目录的选项,只不过它是用来编译汇编源代码的。
例子:
# arch/x86_64/kernel/Makefile
EXTRA_AFLAGS := -traditional
l $(EXTRA_LDFLAGS) 和 $(EXTRA_ARFLAGS)分别与 $(LD)和 $(AR)类似,只不过,他们是针对内核的每个目录的编辑标志。
例子:
# arch/m68k/fpsp040/Makefile
EXTRA_LDFLAGS := -x
l CFLAGS_$@, AFLSGA_$@
CFLAGS_$@ 和 AFLAGS_$@ 只能在当前Kbuild Makefile中的命令中使用。
$(CFLAGS_$@) 是 $(CC) 针对每个文件的选项。$@ 表明了具体操作的目标文件。
例子:
# drivers/scsi/Makefile
CFLAGS_aha152x.o = -DAHA152X_STAT -DAUTOCONF
CFLAGS_gdth.o = # -DDEBUG_GDTH=2 -D__SERIAL__ -D__COM2__ \
-DGDTH_STATISTICS
CFLAGS_seagate.o = -DARBITRATE -DPARITY -DSEAGATE_USE_ASM
以上三行分别设置了aha152x.o,gdth.o 和 seagate.o的编译选项。
l $(AFLAGS_$@)也类似,只是针对汇编语言的。
例子:
# arch/arm/kernel/Makefile
AFLAGS_head-armv.o := -DTEXTADDR=$(TEXTADDR) -traditional
AFLAGS_head-armo.o := -DTEXTADDR=$(TEXTADDR) –traditional
3.9 跟踪依赖
Kbuild 跟踪以下情况的依赖:
1) 所有要参与编译的文件(所有的.c 和.h文件)
2) 在参与编译文件中所要使用的 CONFIG_ 选项
3) 用于编译目标的命令行
因此,如果你改变了 $(CC) 的选项,所有受影响的文件都要重新编译。
3.10 特殊规则
特殊规则就是Kbuild编译系统不能提供所要求的支持时,所使用的规则。一个典型的例子就是在构建过程中生成的头文件。Another example are the architecture-specific Makefiles which need special rules to prepare boot images etc.
Special rules are written as normal Make rules.
Kbuild is not executing in the directory where the Makefile is located, so all special rules shall provide a relative path to prerequisite files and target files.
Two variables are used when defining special rules:
l $(src)
$(src) 表明Makefile所在目录的相对路径。需要定位源代码树中的文件时经常使用该变量。
l $(obj)
$(obj) 表明目标文件所要存储目录的相对路径。经常在定位所生成的目标文件时使用该变量。
例子:
# drivers/scsi/Makefile
$(obj)/53c8xx_d.h: $(src)/53c7,8xx.scr $(src)/script_asm.pl
$(CPP) -DCHIP=810 - < $< | ... $(src)/script_asm.pl
这就是一个特殊规则,遵守着make所要求的普通语法。
目标文件依赖于两个源文件。用$(obj)来定位目标文件,用$(src)来定位源文件。
3.11 $(CC) 支持的函数
内核可能由多个不同版本的$(CC)编译,而每个版本都支持不同的功能集与选项集。Kbuild系统提供了检查 $(CC) 可用选项的基本功能。$(CC)一般情况下是gcc编译器,但也可以使用其它编译器来代替gcc。
l as-option
as-option,当编译汇编文件(*.S)时,用来检查 $(CC) 是否支持特定选项。如果第一个选项不支持的话,可选的第二个选项就派上用场了。
例子:
# arch/sh/Makefile
cflags-y += $(call as-option,-Wa$(comma)-isa=$(isa-y),)
在上面的例子里,如果 $(CC) 支持选项 -Wa$(comma)-isa=$(isa-y),cflags-y就会被赋予该值。 第二个参数是可选的,当第一个参数不支持时,就会使用该值。
l ld-option
ld-option,当链接目标文件时,用来检查 $(CC) 是否支持特定选项。如果第一个选项不支持的话,可选的第二个选项可以用来指定。
例子:
#arch/i386/kernel/Makefile
vsyscall-flags += $(call ld-option, -Wl$(comma)--hash-style=sysv)
在上面的例子中,如果 $(CC)支持选项 -Wl$(comma)--hash-style=sysv,ld-option就会被赋予该值。
第二个参数是可选的,当第一个参数不支持时,就会使用该值。
l cc-option
cc-option,用来检查 $(CC) 是否支持特定选项,并且不支持使用可选的第二项。
例子:
# arch/i386/Makefile
cflags-y += $(call cc-option,-march=pentium-mmx,-march=i586)
在上面的例子中,如果 $(CC)支持选项 -march=pentium-mmx,cc-option就会被赋予该值,否则就赋 -march-i586。
cc-option的第二个参数是可选的。如果忽略的话,当第一个选项不支持时,cflags-y 不会被赋值。
l cc-option-yn
cc-option-yn,用来检查 gcc 是否支持特定选项,返回'y'支持,否则为'n'。
例子:
# arch/ppc/Makefile
biarch := $(call cc-option-yn, -m32)
aflags-$(biarch) += -a32
cflags-$(biarch) += -m32
在上面的例子里,当 $(CC) 支持 -m32选项时,$(biarch)设置为y。当$(biarch) 为y时,扩展的 $(aflags-y) 和 $(cflags-y)变量就会被赋值为:-a32 和 -m32。
l cc-option-align
gcc版本大于3.0时,改变了函数,循环等用来声明内存对齐的选项。当用到对齐选项时,$(cc-option-align) 用来选择正确的前缀:
gcc < 3.00
cc-option-align = -malign
gcc >= 3.00
cc-option-align = -falign
例子:
CFLAGS += $(cc-option-align)-functions=4
在上面的例子中,选项 -falign-funcions=4 被用在gcc >= 3.00的时候。对于小于3.00时, 使用 -malign-funcions=4 。
l cc-version
cc-version以数学形式返回 $(CC) 编译器的版本号。
其格式是:<major><minor>,二者都是数学。比如,gcc 3.41 会返回 0341。 当某版本的 $(CC) 在某方面有缺陷时,cc-version就会很有用。比如,选项-mregparm=3 虽然会被gcc接受,但其实现是有问题的。
例子:
# arch/i386/Makefile
cflags-y += $(shell \
if [ $(call cc-version) -ge 0300 ] ; then \
echo "-meregparm=3"; fi ;)
在上面的例子中,-mregparm=3只会在gcc的版本号大于等于3.0的时候使用。
l cc-ifversion
cc-ifversion测试 $(CC) 的版本号,如果版本表达式为真,就赋值为最后的参数。
例子:
#fs/reiserfs/Makefile
EXTRA_CFLAGS := $(call cc-ifversion, -lt, 0402, -O1)
在这个例子中,如果 $(CC) 的版本小于4.2,EXTRA_CFLAGS就被赋值 -O1。
cc-ifversion 可使用所有的shell 操作符:-eq,-ne,-lt,-le,-gt,和-ge。
第三个参数可以像上面例子一样是个文本,但也可以是个扩展的变量或宏。
----
linux 的 Kernel Makefiles 文档见:
https://01.org/linuxgraphics/gfx-docs/drm/kbuild/makefiles.html
里面有关于kernel makefile 里定义 quiet_cmd_ 自定义command的帮助。
下面是另一个关于Kbuild的帮助:
https://meetonfriday.com/posts/5523c739/
Linux Kbuild主要有四個部分:
- .config: kernel相關設置
- arch/$(ARCH)/Makefile: 與架構相關的Makefile
- scripts/Makefile.*: Kbuild的一些規則
- 根目錄下的Makefile以及其他資料夾下的Makefiles
Kbuild的用法相當簡單,對於一個driver的撰寫者最常會碰到的就2種:
obj-y
: 將module編進kernel中- 注意對於
obj-y
直接將module編進kernel時,編譯的順序很重要,因為這會影響開機時module init的順序,如果順序沒搞好可能就會遇到問題!可以參考[Linux Kernel慢慢學]Linux modules載入及載入順序
- 注意對於
obj-m
: 將module編成.ko檔,以便之後可以進行載入
其他還有產生library的lib-y
、產生executable的hostprogs-y
…有興趣的再自己去看
那Kbuild要怎麼進入一個資料夾呢? 透過obj-$(CONFIG_) += directory/
的方式
- 在kernel code裡面較少使用上面番外篇的遞迴形式(make -C directory/ )來跑所有的子目錄,因為通常kernel的Makefile還會搭配一堆config,這些config可以讓你選擇這次編譯時要將哪些module編進來哪些不要,所以會需要一個一個資料夾的方式條列在Makefile內
支付宝扫一扫捐赠
微信公众号: 共鸣圈
欢迎讨论,邮件: 924948$qq.com 请把$改成@
QQ群:263132197
QQ: 924948