现在的位置: 首页 > 综合 > 正文

Linux内核模块创建

2018年05月08日 ⁄ 综合 ⁄ 共 8600字 ⁄ 字号 评论关闭
本文包含了以下四个方面的信息:
--怎样构建一个外部模块
--怎样使用的你的kbuild结构编译/建造(make)一个模块
--Kbuild如何安装一个内核
--怎样在一个非标准的位置安装一个内核
===目录
===1、概述
===2、如何构建一个外部模块
---2.1
构建外部模块
---2.2
可用目标
---2.3
可用选项
---2.4
为构建模块准备内核树
---2.5
为模块创建单独的文件
===3、命令例子
===4、创建一个外部模块的kbuild文件
===5、包含文件
---5.1
怎样从内核include目录包含文件
---5.2
外部模块使用一个 include/目录
---5.3
外部模块使用各自不同的数个目录
===6、安装模块
---6.1
INSTALL_MOD_PATH
---6.2
INSTALL_MOD_DIR
===7、Module versioning 和 Module.symvers
---7.1
来自内核的符号(vmlinux + modules)
---7.2
符号和外部模块
---7.3
来自另一个外部模块的符号
===8、要点和技巧
---8.1
测试CONFIG_FOO_BAR
1、介绍
----------------------
kbuild同时包含了在内核源文件树中和树外构建模块的功能。后者通常被称作外部模块(或“out-of-tree”),通常被用于开发过程中的功能测试或是未计划包含在内核中的模块。
本文包含了构建模块所需的主要信息。构建一个外部模块需要一个makefile,它屏蔽大部分的复杂操作,使得仅需键入“make”即可完成构建任务。第四章会有一个完整的例子,“为一个外部模块创建kbuild文件”。
2、怎样构建一个外部模块
-----------------------------
kbuild提供了构建外部模块的功能,首先要有一个预先编译好的,所有源文件可用的内核。编译内核时可用的目标子集在编译模块时同样可用。
---2.1 构建一个外部模块
使用下列命令构建一个外部模块:
make -C <path-to-kernel> M='pwd'
###pwd:当前目录
为当前正在运行的内核构建使用命令:
make -C /lib/modules/'uname -r'/build M='pwd'                        ###uname -r 获取当前内核的(发布)版本号
为了上述命令能够正确运行,内核必须已经被构建过且使能了模块特性(内核支持模块)。
安装模块:
make -C <path-to-kernel> M='pwd' modules_install
这只是一些简单的命令,后面会有更复杂的示例。
---2.2 可用目标
$KDIR代表内核源代码树的根目录。
make -C $KDIR M='pwd' ###-C:Make选项,切换到-C参数所在目录
在当前目录构建模块。所有的输出文件将被放置到与源文件相同的目录。此命令不会更新内核源码文件,且要求内核已经成功进行了编译。
make -C $KDIR M='pwd' modules
如果没有给定目标则构建modules目标。                                         ###如果目标没有指定也是如此。(可依据前面的叙述获取更多的信息)
make -C $KDIR M=`pwd` modules_install
安装外部模块。默认安装在/lib/modules/<kernel-version>/extra目录中,但是也可由INSTALL_MOD_PATH修改(查看INSTALL_MOD_PATH部分章节)
make -C $KDIR M=`pwd` clean
移除所有编译模块时产生的文件
#不会对内核产生影响
make -C $KDIR M=`pwd` help
help列出所有构建外部模块时可用的目标。
---2.3 可用选项
$KDIR代表内核源代码树的根目录。
make -C $KDIR
指定内核源文件目录。'$KDIR'提供了内核源文件所在的目录。make在执行时切换到指定的目录,并在结束时切换回来。
make -C $KDIR M='pwd'
'M='用于告诉kbuild一个外部模块正在被构建。给定的'M='选项是外部模块所在的目录。当外部模块被构建时只有通用目标的一个子集可供使用。
make -C $KDIR SUBDIRS=`pwd`
‘SUBDIRS=’与'M='相同。保留'SUBDIRS='语法只是是为了兼容以前的版本。
---2.4 为创建模块准备内核树
确认内核包含构建外部模块必须使用的'modules_prepare'目标所需的信息。'modules_prepare'是唯一一个为外部模块准备内核源码树的简单方法。
注意:即使CONFIG_MODVERSIONS被设置,modules_prepare也不会创建Module.symvers文件。因此一个完整的内核构建需要被完成,以用于使module versioning能够工作。
---2.5 为模块构建单独的文件
构建模块的一个单独的文件(模块的一部分)是可能的。它可以为内核、模块、甚至是外部模块同样好的工作。
例子(foo.ko模块,由bar.o和baz.o构成)
make -C $KDIR M=`pwd` bar.lst
make -C $KDIR M=`pwd` bar.o
make -C $KDIR M=`pwd` foo.ko
make -C $KDIR M=`pwd` /
3、命令示例
-----------------
这个例子展示了为当前正在运行的内核创建一个外部模块时执行的命令。在下例中,分配过程期望使用一个目录来为在另外一个目录编译的内核放置输出文件,而不是在内核源下,但是,当源文件和输出文件混合在一个目录时,这个例子也可以正常工作。
#Kernel source内核源代码
/lib/modules/<kernel-version>/source -> /usr/src/linux-<version>
#Output from kernel compile内核编译输出
/lib/modules/build -> /usr/src/linux-<version>/-up
切换到kbuild文件所在的目录,然后执行下面的命令来构建模块:
cd /home/user/src/module
make -C /usr/src/'uname -r'/source O=/lib/modules/'uname -r'/build M='pwd'
接下来,使用下列命令安装模块
make -C /usr/src/'uname -r'/source O=/lib/modules/'uname -r'/build M='pwd' modules_install
如果仔细观察,你可以看到这与前面所列的命令相同,只是目录被拼写出来了而已。
这是一个相当长的命令,后续章节会介绍一些技巧以简化它。
4、为外部模块创建一个kbuild文件
----------------------------------------
kbuild是内核的构建系统,外部模块必须使用kbuild保持与构建系统的兼容,并且获取正确的GCC选项。
kbuild文件作为输入,应当遵循内核Makefile文件的语法。此部分介绍一些技巧用于处理外部模块。
接下来我们将为一个包含下列文件的模块创建makefile
8123_if.c
8123_if.h
8123_pci.c
8123_bin.o_shipped
<= Binary blob
---4.1 为模块和内核共享makefile
一个外部模块通常包含一个封装好的Makefile文件,支持使用'make'命令(不附带任何参数)来编译模块。如果makefile包含了一些诸如测试目标等附加功能(很有可能),它们需要从kbuild中过滤出去,因为如果出现名字冲突的话,它可能影响kbuild。
例1:
--->文件名: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
KERNELDIR := /lib/modules/'uname -r'/build
all::
$(MAKE) -C $(KERNELDIR) M='pwd' $@
#module specific target
genbin:
echo "X" > 8213_bin.o_shipped
endif
在例1中,对于KERNELRELEASE的检查用于分离Makefile的两个部分。kbuild只做这个分割工作,而make将关注除了分割外的所有东西。
在最近的内核版本中,kbuild将优先寻找一个名为Kbuild的文件,Makefile只是第二选择。
利用Kbuild文件,使我们将例1中的Makefile文件分为例2所示两个部分。
例2:
--->文件名:Kbuild
obj-m  := 8123.o
8123-y := 8123_if.o 8123_pci.o 8123_bin.o
--->文件名:Makefile
KERNELDIR := /lib/modules/`uname -r`/build
all::
$(MAKE) -C $(KERNELDIR) M=`pwd` $@
# Module specific targets
genbin:
echo "X" > 8123_bin.o_shipped
在例2中我们得到了两个相当简单的文件。对于例子中所用的简单文件来讲,似乎没有必要分割。但有些外部模块会使用多达数百行的Makefile,此时将kbuild部分从其余部分分离出来就变得很有意义了。
例3展示了一个向前兼容的版本:
--->文件名:Kbuild
obj-m  := 8123.o
8123-y := 8123_if.o 8123_pci.o 8123_bin.o
--->文件名:Makefile
ifneq ($(KERNELRELEASE),)
include Kbuild
else
# Normal Makefile
KERNELDIR := /lib/modules/`uname -r`/build
all::
$(MAKE) -C $(KERNELDIR) M=`pwd` $@
# Module specific targets
genbin:
echo "X" > 8123_bin.o_shipped
endif
这里的技巧是从Makefile中包含Kbuild文件,所以如果一个老的kbuild版本使用此Makefile,Kbuild文件将被包含。
---4.2 模块包含二进制blob
一些外部模块需要包含a .o文件作为一个blob。kbuild支持这样操作,但要求blob文件需要以_shipped为后缀,即文件名为<filename>_shipped。
在我们的例子中,blob名为8123_bin.o_shipped,并且当kbuild规则引入(生效)时,8123_bin.o文件被简单的创建为8213_bin.o_shipped文件的一个拷贝(剥离_shipped文件名后缀)。这将允许为模块指定文件时使用8123_bin.o文件名。
例4:
obj-m  := 8123.o
8123-y := 8123_if.o 8123_pci.o 8123_bin.o
例4中对待二进制文件与普通的.c/.h文件没有区别。但kbuild将使用不同的规则来创建.o文件。
5、包含文件
---------------------
当一个C文件使用别的C文件中的东西时,包含文件是必需的(在C语义中并不严格要求这样做,但如果想要良好的编码实现则需要遵照)。一些包含多个.c文件的模块,通常每个.c文件有一个.h文件。
- 如果一个.h文件只描述了模块的内部接口,这个.h文件应该被放置到.c相同的目录。
- 如果一个.h文件声明了一个被别的内核模块(别的目录下)使用的接口,需要被恰当放置到include/linux或include目录。
一个例外是那些在include目录下拥有自己目录的大文件系统,如include/scsi。还有一个例外是体系结构指定的.h文件,需要被放置在include/asm-$(ARCH)/*目录。
外部模块趋向于将包含文件放置到一个单独的include/目录,因此需要在它们的kbuild文件中做些额外处理。
---5.1 怎样从内核include目录包含文件
当一个模块需要从include/linux/目录包含文件,只需使用:
#include <linux/modules.h>
kbuild将确认添加必要的gcc选项以使相关的目录被查找。
同样的,对于一个放置在与.c文件相同目录的.h文件,
#include "8123_if.h"
语句也会做同样的工作。
---5.2 外部模块使用一个include/目录
外部模块经常将它们的.h文件放置到一个单独的include/目录,尽管这并不是通常的内核风格。当一个外部模块使用一个include/目录时,kbuild需要被告知。这里的技巧是使用EXTRA_CFLAGS变量(对所有的.c文件生效)或是CFLAGS_$F.o(只对一个单独的文件生效)。
在我们的例子中,如果将8123_if.h移到一个名为include/的子目录,结果Kbuild文件如下:
--->文件名:Kbuild
obj-m  := 8123.o
EXTRA_CFLAGS := -Iinclude
8123-y := 8123_if.o 8123_pci.o 8123_bin.o
注意:指定搜索路径时,-I和路径之间必须没有空格。
---5.3 外部模块使用数个目录
如果一个外部模块将文件散布到多个目录(与内核风格不符),kbuild也可以处理这种情况
假设如下例子:
|
+- src/complex_main.c
|   +- hal/hardwareif.c
|   +- hal/include/hardwareif.h
+- include/complex.h
为了构建一个名为complex.ko的单独模块,我们需要如下kbuild文件:
Kbuild:
obj-m := complex.o
complex-y := src/complex_main.o
complex-y += src/hal/hardwareif.o
EXTRA_CFLAGS := -I$(src)/include
EXTRA_CFLAGS += -I$(src)src/hal/include
尽管这不是一个推荐的习惯,但kbuild确实知道如何处理放置在另一个目录中的.o文件。方法是指定和kbuild文件所在目录相关的路径。
为了找到.h文件,我们需要明确地告诉kbuild在哪里寻找.h文件。当kbuild执行时,当前目录通常是内核树的根目录(-C的参数),因此我们需要使用绝对路径告诉kbuild如何查找.h文件。当创建一个外部模块时,$(src)值指定了Kbuild文件所在的目录。因此,-I$(src)/用于指出Kbuild文件所在的目录,附加的路径可以使用它来进行扩展。
6、安装模块
------------------
内核内部模块被安装到目录:
/lib/modules/$(KERNELRELEASE)/kernel
外部模块被安装到目录:
/lib/modules/$(KERNELRELEASE)/extra
---6.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时使用命令行指定。INSTALL_MOD_PATH对于安装内核内部模块和外部模块均起作用。
---6.2 INSTALL_MOD_DIR
安装一个外部模块时,默认路径为/lib/modules/$(KERNELRELEASE)/extra,但通常我们希望将一个独立功能模块放置到一个单独的目录。为此,我们可以使用INSTALL_MOD_DIR来指定一个目录名代替“extra”。
$ make INSTALL_MOD_DIR=gandalf -C KERNELDIR \
M=`pwd` modules_install
=> Install dir: /lib/modules/$(KERNELRELEASE)/gandalf
7、Module versioning & Module.symvers
-------------------------------------------------
Module versioning通过CONFIG_MODVERSIONS来使能,它可以用作一个简单的ABI一致性检查。
Module versioning生成一个所有导出符号原型的CRC值,当一个模块加载/使用时,包含在内核中的CRC值与模块中的相比较,若不相等,则内核拒绝加载此模块。
Module.symvers包含了一个所有从内核构建中导出的符号的列表。
---7.1 源于内核的符号(vmlinux + modules)
在内核构建过程中,会生成一个名为Module.symvers的文件。Module.symvers包含了所有从内核和已编译的模块导出的符号。对于每个符号,对应的CRC值也被保存。
Module.symvers文件的语法如下:
<CRC>       <Symbol>           <module>
示例:
0x2d036834  scsi_remove_host   drivers/scsi/scsi_mod
对于一个没有使能CONFIG_MODVERSIONS的内核构建来说,读CRC值时默认0x00000000。
Module.symvers用于两个目的:
1、它列出了vmlinux和所有模块中导出的符号。
2、当CONFIG_MODVERSIONS使能时,它列出了CRC值。
---7.2 符号和外部模块
当构建一个外部模块时,构建系统需要访问来自内核的符号以检查是否所有的外部符号被定义。这个工作在MODPOST步骤完成,modpost从内核中读取Module.symvers文件且得到所有的符号。如果一个被构建的外部模块所在的目录中提供了Module.symvers文件,则此文件也会被读取。在MODPOST步骤,会创建一个新的Module.symvers文件,包含了所有没在内核中定义的导出符号。
---7.3 来自于另外模块的符号
有时,一个外部模块会使用来自另外一个外部模块导出的符号。Kbuild需要了解所有的符号,以避免报出关于未定义符号的警告。
存在三种方法来使内核了解到多于一个外部模块的所有符号。
使用一个顶层 kbuild文件是推荐的方法,但是在一些情况下可能不太合适。
1、使用一个顶层kbuild文件
如果你有两个模块:'foo'和'bar',且foo需要来自'bar'的符号,然后可以用一个通用的顶层kbuild文件,这样两个模块可以在同一次编译中构建。
假设如下目录结构:
./foo/ <= 包含foo模块
./bar/ <= 包含bar模块
顶层kbuild文件看起来如下:
#./Kbuild:(也可能被命名为Makefile)
obj-y := foo/ bar/
执行:
make -C $KDIR M=`pwd`
将编译这两个模块。
2、使用一个外部的Module.symvers文件:
当编译一个外部模块时,会生成一个包含了所有未在内核中定义的导出符号的Module.symvers文件。为了能够使用源自模块‘bar’的符号,可以将已完成编译的bar模块生成的Module.symvers文件,拷贝到foo模块的构建目录。
在模块构建过程中,kbuild将读取外部模块目录下的Module.symvers文件,当构建完成时,会创建一个新的Module.symvers文件,包含了所有模块使用的且未被内核定义的符号。
3、在Makefile中使用make变量KBUILD_EXTRA_SYMBOLS
如果从另外一个模块中拷贝Module.symvers文件不现实,你可以在Makefile中为KBUILD_EXTRA_SYMBOLS变量指定一个空格分割的文件列表。modpost在初始化它的符号表的过程中会调用这些文件。
8、技巧要点
------------------
---8.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_定义时使用与内核内部模块一样的方式。
【上篇】
【下篇】

抱歉!评论已关闭.