=========================
构建外部模块
本文档介绍了如何构建树外内核模块。
… 目录
=== 1简介
=== 2如何构建外部模块
-2.1命令语法
-2.2选项
-2.3目标
-2.4建立单独的文件
=== 3.为外部模块创建Kbuild文件
-3.1共享Makefile
— 3.2分开的Kbuild文件和Makefile
-3.3二进制斑点
— 3.4构建多个模块
=== 4.包含文件
— 4.1内核包含
-4.2单个子目录
— 4.3几个子目录
=== 5.模块安装
-5.1 INSTALL_MOD_PATH
-5.2 INSTALL_MOD_DIR
=== 6.模块版本控制
— 6.1来自内核的符号(vmlinux +模块)
— 6.2符号和外部模块
— 6.3来自另一个外部模块的符号
=== 7.技巧与窍门
-7.1测试CONFIG_FOO_BAR
1.简介
“ kbuild”是Linux内核使用的构建系统。模块必须使用kbuild来与构建基础结构中的更改保持兼容,并为“ gcc”选择正确的标志。提供了用于在树内和树外构建模块的功能。两者的构建方法都相似,并且所有模块最初都是在树外开发和构建的。
本文档涵盖面向有兴趣构建树外(或“外部”)模块的开发人员的信息。外部模块的作者应提供一个隐藏大多数复杂性的makefile,因此只需键入“ make”即可构建该模块。这很容易实现,第3节将提供一个完整的示例。
2.如何构建外部模块
要构建外部模块,您必须具有可用的预构建内核,其中包含在构建中使用的配置和头文件。
另外,必须在启用了模块的情况下构建内核。如果您使用的是发行版内核,那么发行版将提供您正在运行的内核的软件包。
另一种选择是使用“ make”目标“ modules_prepare”。这将确保内核包含所需的信息。目标仅作为准备用于构建外部模块的内核源代码树的简单方法而存在。
注意:即使设置了CONFIG_MODVERSIONS,“ modules_prepare”也不会生成Module.symvers。因此,需要执行完整的内核构建以使模块版本控制工作。
2.1命令语法
构建外部模块的命令是:
$ make -C <path_to_kernel_src> M=$PWD
由于命令中提供了"M=<dir>
"选项,kbuild系统知道正在构建外部模块。
要针对正在运行的内核进行构建,请使用:
$ make -C /lib/modules/`uname -r`/build M=$PWD
然后要安装刚刚构建的模块,请将目标“ modules_install”添加到命令中:
$ make -C /lib/modules/`uname -r`/build M=$PWD modules_install
2.2选项
($ KDIR指向内核源目录的路径。)
make -C $KDIR M=$PWD
-C $KDIR
内核源所在的目录。
实际上,“ make”将在执行时更改为指定目录,并在完成后切换回原来的目录。
M=$PWD
通知kbuild正在构建外部模块。
赋予“ M”的值是外部模块(kbuild文件)所在目录的绝对路径
。
2.3目标
构建外部模块时,仅“ make”目标的子集可用。
make -C $KDIR M=$PWD [target]
默认值将构建位于当前目录中的模块,因此不需要指定目标。所有输出文件也将在此目录中生成。没有尝试更新内核源代码,这是对内核执行成功的“ make”的前提。
modules
外部模块的默认目标。它具有与未指定目标相同的功能。请参阅上面的描述。
modules_install
安装外部模块。默认位置是/lib/modules/<kernel_release>/extra/
,但是可以在INSTALL_MOD_PATH中添加一个前缀(在第5节中讨论)。
clean
仅删除模块目录中的所有生成的文件。
help
列出外部模块的可用目标。
2.4建立单独的文件
可以构建模块中的单个文件。
这对于内核,模块甚至外部模块同样有效。
示例(模块foo.ko,由bar.o和baz.o组成):
make -C $KDIR M=$PWD bar.lst
make -C $KDIR M=$PWD baz.o
make -C $KDIR M=$PWD foo.ko
make -C $KDIR M=$PWD ./
3.为外部模块创建Kbuild文件
在上一节中,我们看到了为正在运行的内核构建模块的命令。但是,实际上并没有构建该模块,因为需要一个构建文件。该文件中包含的是正在构建的模块的名称,以及必需的源文件的列表。该文件可能很简单,只需一行:
obj-m := <module_name>.o
kbuild系统将从<module_name> .c构建<module_name> .o,并在链接后生成内核模块<module_name> .ko。
上面的行可以放在“ Kbuild”文件或“ Makefile”中。
当从多个来源构建模块时,需要另外一行列出文件:
<module_name>-y := <src1>.o <src2>.o ...
注意:有关描述kbuild使用的语法的更多文档位于Documentation / kbuild / makefiles.rst中。
以下示例演示了如何为模块8123.ko创建一个构建文件,该文件是从以下文件构建的:
8123_if.c
8123_if.h
8123_pci.c
8123_bin.o_shipped <= Binary blob
3.1共享的Makefile
外部模块始终包含包装器makefile,该文件支持使用不带参数的“ make”来构建模块。
kbuild不使用该目标。这只是为了方便。
可以包括其他功能,例如测试目标,但由于名称冲突,应将其从kbuild中过滤掉。
示例1:
--> filename: Makefile
ifneq ($(KERNELRELEASE),)
# kbuild part of makefile
obj-m := 8123.o
8123-y := 8123_if.o 8123_pci.o 8123_bin.o
else
# normal makefile
KDIR ?= /lib/modules/`uname -r`/build
default:
$(MAKE) -C $(KDIR) M=$$PWD
# Module specific targets
genbin:
echo "X" > 8123_bin.o_shipped
endif
KERNELRELEASE的检查用于分隔makefile的两个部分。在示例中,kbuild将仅看到两个分配,而“ make”将看到除这些分配以外的所有内容
两个作业。这是由于对该文件进行了两次传递:
第一遍是通过在命令行上运行的“ make”实例进行的;第二遍是kbuild系统,它是由默认目标中的参数化“ make”启动的。
3.2分开的Kbuild文件和Makefile
在较新版本的内核中,kbuild将首先查找名为“ Kbuild”的文件,并且只有在找不到该文件的情况下,它才会查找makefile。利用“ Kbuild”文件,我们可以
将示例1中的makefile分成两个文件:
示例2:
--> filename: Kbuild
obj-m := 8123.o
8123-y := 8123_if.o 8123_pci.o 8123_bin.o
--> filename: Makefile
KDIR ?= /lib/modules/`uname -r`/build
default:
$(MAKE) -C $(KDIR) M=$$PWD
# Module specific targets
genbin:
echo "X" > 8123_bin.o_shipped
由于每个文件的简单性,示例2中的拆分值得怀疑。但是,某些外部模块使用的makefile由几百行组成,在这里,将kbuild部分与其余部分分开确实很有意义。
下一个示例显示了向后兼容的版本。
示例3:
--> filename: Kbuild
obj-m := 8123.o
8123-y := 8123_if.o 8123_pci.o 8123_bin.o
--> filename: Makefile
ifneq ($(KERNELRELEASE),)
# kbuild part of makefile
include Kbuild
else
# normal makefile
KDIR ?= /lib/modules/`uname -r`/build
default:
$(MAKE) -C $(KDIR) M=$$PWD
# Module specific targets
genbin:
echo "X" > 8123_bin.o_shipped
endif
这里的“ Kbuild”文件包含在makefile中。这样,当将“ make”和kbuild部件拆分为单独的文件时,可以使用仅知道makefile的较旧版本的kbuild。
3.3二进制斑点
一些外部模块需要包含一个目标文件作为Blob。
kbuild支持此功能,但要求将blob文件命名为 _shipped。当执行kbuild规则时,将创建 _shipped的副本,并删除_shipped,
给我们<文件名>。缩短的文件名可用于分配给模块。
在本节中,一直使用8123_bin.o_shipped来构建内核模块8123.ko。它已被包含为8123_bin.o ::
8123-y := 8123_if.o 8123_pci.o 8123_bin.o
尽管普通源文件和二进制文件之间没有区别,但是kbuild在为模块创建目标文件时会采用不同的规则。
3.4构建多个模块
kbuild支持使用一个构建文件构建多个模块。例如,如果您要构建两个模块foo.ko和bar.ko,则kbuild行将是:
obj-m := foo.o bar.o
foo-y := <foo_srcs>
bar-y := <bar_srcs>
就这么简单!
4.包含文件
在内核中,头文件根据以下规则保存在标准位置:
*如果头文件仅描述模块的内部接口,则该文件将与源文件放在同一目录中。
*如果头文件描述了位于其他目录中的内核其他部分使用的接口,则该文件将放置在include/linux/
中。
注意:
该规则有两个值得注意的例外:较大的子系统在include /下有自己的目录,例如include / scsi;和特定于体系结构的标头位于arch / $(ARCH)/ include /下。
4.1内核包含
要包含位于include / linux /下的头文件,只需使用:
#include <linux / module.h>
kbuild将在“ gcc”中添加选项,以便搜索相关目录。
4.2单个子目录
外部模块倾向于将头文件放在其源所在的单独include /目录
中,尽管这不是通常的内核样式。要通知kbuild目录,请使用ccflags-y
或CFLAGS_<filename>.o
。
使用第3节中的示例,如果我们将8123_if.h移至名为include的子目录,则生成的kbuild文件将如下所示:
--> filename: Kbuild
obj-m := 8123.o
ccflags-y := -Iinclude
8123-y := 8123_if.o 8123_pci.o 8123_bin.o
请注意,在分配中,-I和路径之间没有空格。这是kbuild的局限性:必须没有空格。
4.3几个子目录
kbuild可以处理分布在多个目录中的文件。
考虑以下示例:
.
|__ src
| |__ complex_main.c
| |__ hal
| |__ hardwareif.c
| |__ include
| |__ hardwareif.h
|__ include
|__ complex.h
要构建模块complex.ko,我们需要以下kbuild文件:
--> filename: Kbuild
obj-m := complex.o
complex-y := src/complex_main.o
complex-y += src/hal/hardwareif.o
ccflags-y := -I$(src)/include
ccflags-y += -I$(src)/src/hal/include
如您所见,kbuild知道如何处理其他目录中的目标文件。技巧是指定相对于kbuild文件位置的目录。话虽如此,不建议您这样做。
对于头文件,必须明确告知kbuild在哪里查找。执行kbuild时,当前目录始终是内核树的根(“ -C”的参数),因此需要绝对路径。 $(src)
通过指向当前正在执行的kbuild文件所在的目录来提供绝对路径。
5.模块安装
内核中包含的模块安装在以下目录中:
/lib/modules/$(KERNELRELEASE)/kernel/
外部模块安装在:
/lib/modules/$(KERNELRELEASE)/extra/
5.1 INSTALL_MOD_PATH
以上是默认目录,但与往常一样,可以进行一定程度的自定义。可以使用变量INSTALL_MOD_PATH将前缀添加到安装路径:
$ make INSTALL_MOD_PATH=/frodo modules_install
=> Install dir: /frodo/lib/modules/$(KERNELRELEASE)/kernel/
可以将INSTALL_MOD_PATH
设置为普通的shell变量,或者如上所示,可以在调用“ make”时在命令行上指定。这在安装树内和树外模块时均有效。
5.2 INSTALL_MOD_DIR
默认情况下,外部模块安装在/lib/modules/$(KERNELRELEASE)/extra/
下的目录中,但是您可能希望在单独的目录中找到具有特定功能的模块。为此,请使用INSTALL_MOD_DIR为“额外”指定替代名称:
$ make INSTALL_MOD_DIR=gandalf -C $KDIR M=$PWD modules_install
=> Install dir: /lib/modules/$(KERNELRELEASE)/gandalf/
6.模块版本控制
通过CONFIG_MODVERSIONS标记启用模块版本控制,并将其用作简单的ABI一致性检查。为导出的符号创建完整原型的CRC值。加载/使用模块时,会将内核中包含的CRC值与模块中的相似值进行比较;如果它们不相等,则内核拒绝加载模块。
Module.symvers
包含内核构建中所有导出的符号的列表。
6.1来自内核的符号(vmlinux +模块)
在内核构建过程中,将生成一个名为Module.symvers的文件。 Module.symvers包含从内核和编译模块中导出的所有符号。对于每个符号,还存储相应的CRC值。
Module.symvers文件的语法为:
<符号> <模块> <导出类型> <名称空间>
<CRC> <Symbol> <Module> <Export Type> <Namespace>
0xe1cc2a05 usb_stor_suspend drivers/usb/storage/usb-storage EXPORT_SYMBOL_GPL USB_STORAGE
这些字段由制表符分隔,并且值可以为空(例如,如果未为导出的符号定义名称空间)。
对于未启用CONFIG_MODVERSIONS的内核构建,CRC将读取0x00000000。
Module.symvers有两个用途:
1)列出了从vmlinux和所有模块导出的所有符号。
2)如果启用了CONFIG_MODVERSIONS,它将列出CRC。
6.2符号和外部模块
在构建外部模块时,构建系统需要访问内核中的符号,以检查是否定义了所有外部符号。这是在MODPOST步骤中完成的。 modpost通过从内核源代码树中读取Module.symvers获得符号。在MODPOST步骤中,将写入一个新的Module.symvers文件,其中包含从该外部模块导出的所有符号。
6.3来自另一个外部模块的符号
有时,外部模块使用从另一个外部模块导出的符号。 Kbuild需要全面了解所有符号,以避免吐出有关未定义符号的警告。针对这种情况存在两种解决方案。
注意:建议使用带有顶级kbuild文件的方法,但在某些情况下可能不切实际。
使用顶级kbuild文件
如果您有两个模块foo.ko和bar.ko,其中foo.ko需要来自bar.ko的符号,则可以使用公共的顶层kbuild文件,以便两个模块都在同一构建中编译。请考虑以下目录布局::
./foo/ <= contains foo.ko
./bar/ <= contains bar.ko
顶级kbuild文件将如下所示:
#./Kbuild (or ./Makefile):
obj-m := foo/ bar/
并执行:
$ make -C $KDIR M=$PWD
然后将执行预期的操作,并在完全了解两个模块中的符号的情况下编译两个模块。
使用“ make”变量KBUILD_EXTRA_SYMBOLS
如果添加顶级kbuild文件是不切实际的,则可以在构建文件中为KBUILD_EXTRA_SYMBOLS分配一个用空格分隔的文件列表。
这些文件将在其符号表初始化期间由modpost加载。
7.技巧与窍门
7.1测试CONFIG_FOO_BAR
模块通常需要检查某些“ CONFIG_”选项,以决定模块中是否包含特定功能。在kbuild中,这是通过直接引用CONFIG_变量来完成的:
#fs/ext2/Makefile
obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y := balloc.o bitmap.o dir.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o
传统上,外部模块使用“ grep”直接在.config中检查特定的“ CONFIG_”设置。此用法已损坏。如前所述,外部模块应使用kbuild进行构建,因此在测试“ CONFIG_”定义时可以使用与树内模块相同的方法。