编译kernel外部模块
翻译:kernel/Documents/kbuild/modules.txt
该篇文章介绍怎么编译kernel代码结构之外的模块
一、说明
“kbuild”是kernel的编译系统。模块使用kbuid编译,使kernel编译系统保持其兼容性,编译框架修改不影响模块的编译,编译系统确保模块使用gcc使用正确的编译标记。kernel编译提供kernel结构内和结构外编译功能,无论结构内还是结构外,模块编译的方式是类似的,最初的kernel 模块编译都是结构外编译方式。
该文档主要介绍kernel结构外编译外部模块,如何实现隐藏复杂的内部编译逻辑,仅简单实现,使用“make”就可以编译外部模块。
二、编译外部模块
条件
- 存在完整的头文件和配置文件且已预编译完成
- 已完全编译过的kernel
以上条件说明,完全编译过的kernel,删掉其中的源文件(即.c文件),也可以编译外部模块。当和第三方合作,不想暴露逻辑实现,有需要给对方提供kernel编译环境时,可以采用这种方式。分布式kernel编译在次不做说明
语法
- 编译命令
依赖源码编译
编译外部模块的命令是:
make -C <path_to_kernel_src> M=$PWD
选项"M=<dir>"告诉kernel编译系统编译外部模块的源码路径。
依赖运行时环境编译
当依赖一个运行时kernel编译时,使用如下命令:
make -C /lib/modules/`uname -r`/build M=$PWD
运行时环境如下
当编译完成就安装编译出来的模块,在命令中加上“modules_install”目标,命令如下:
make -C /lib/modules/`uname -r`/build M=$PWD modules_install
- 编译选项
make -C $KDIR M=$PWD
- -C $DIR kernel源码路径,编译过程中会改变该路径下内容,编译完成后回退修改,还原到编译前
- M=$PWD 外部模块的源码目录。M的值必须时绝对路径
- 编译目标
编译外部模块时,仅编译系统的几个编译目标可以被使用
make -C $KDIR M=$PWD [target]
编译外部模块,不会更新kernel源码,编译产生的文件在源码目录。编译使用make即可,不需要指定目标。
- modules 外部模块的默认编译目标
- modules_install 基于运行时kernel编译,编译完成后把编译目标安装到/lib/modules/<kernel_release>/extra/目录,或者INSTALL_MOD_PATH指定的目录。
- clean 删除编译产生的文件和目录
- help 列出编译出的模块
- 编译单个文件
编译模块时,可以逐个编译模块的源文件,该种编译方式,编译kernel、模块、外部模块都可以,没有什么区别。
该编译方法,例: 模块foo.ko包含bar.o、baz.o,单个编译方式如下
make -C $KDIR M=$PWD bar.o
make -C $KDIR M=$PWD baz.o
make -C $KDIR M=$PWD foo.ko
make -C $KDIR M=$PWD /
三、编译脚本
编写脚本
创建一个编译外部模块的脚本文件,文件中包含编译模块的名称,和编译需要的源码等。
模块名称
<moudle_name1>.o
编译系统编译模块时,会把<moudle_name1>.c编译为<moudle_name1>.o。当编译链接完成,编译最终生成<moudle_name1>.ko。以上内容,可以写在“Kbuild”文件或者Makefile中。如果一个外部模块编译,依赖多个源文件,可以增加使用以下方式指明源文件
<module_name>-y := <src1>.o <src2>.o ...
注意:更深入的介绍语法的内容在kernel文档“Documentation/kbuild/makefiles.txt”。
脚本的几种实现方式
前提
编译外部模块8123.ko,模块包含文件:
8123_if.c
8123_if.h
8123_pci.c
8123_bin.o_shipped <= Binary blob
方式1:共享Makfile
类似开发中的包装模式,直接是使用make编译,不需要参数。编译目标没有被编译系统使用,可以被编译系统包含,但是不要这么做,可能会存在编译冲突,建议以外部编译方式,单独存放编译文件。
实现如下
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中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义,所以make将读取执行else之后的内容,如果make的目标是clean,直接执行clean操作,然后结束。
当make的目标为all时,-C $(KDIR)指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。else之前的内容为kbuild语法的语句,指明模块源码中各文件的依赖关系,以及要生成的目标模块名。param-objs := file1.o file2.o 表示param.o由file1.o与file2.o 连接生成,obj-m := param.o表示编译连接后将生成param.o模块。
方式二:编译脚本拆分来为Kbuild和Makefile
在新版本的kernel编译系统,首先寻找Kbuild,再寻找Makefile。利用该机制,第一种方式的编译脚本可以分为两部分
file1:Kbuild
obj-m := 8123.o
8123-y := 8123_if.o 8123_pci.o 8123_bin.o
file2:Makfile
KDIR ?= /lib/modules/`uname -r`/build
default:
$(MAKE) -C $(KDIR) M=$$PWD
# Module specific targets
genbin:
echo "X" > 8123_bin.o_shipped
这种实现方式看起来很愚蠢。但是当模块的源文件很多,有几百行时,这种方式就很有用了。
方式3:向后兼容
file1:Kbuild
obj-m := 8123.o
8123-y := 8123_if.o 8123_pci.o 8123_bin.o
file2:Makfile
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被Makfile包含,使早期只寻找Makefile的编译系统也可以使用。
方式4:包含二进制文件
有些外部模块编译需要二级制文件,kbuild编译系统支持这种需求,但是二进制文件命名为特定模式:_shipped。当编译时,编译系统会把_shipped去掉。
编译8123.ko,8123_bin.o_shipped被编译到,使用如下方式:
8123-y := 8123_if.o 8123_pci.o 8123_bin.o
尽管看起来,和编译普通文件没有什么区别,但是对于编译系统,编译时针对不同文件采用了不同的编译规则。
方式5:多模块编译
kbuild编译系统支持一个编译文件种同时编译多个模块。例如同时编译foo.ko、bar.ko,相关修改如下:
obj-m := foo.o bar.o
foo-y := <foo_srcs>
bar-y := <bar_srcs>
是不是很简单!
四、编译包含头文件
kernel头文件放置规则
- 如果头文件只是描述一个模块的内部接口,头文件和源码放置在相同位置
- 如果头文件描述的接口被不同目录的模块使用,需要把头文件放置在include/linux/。
注意: 有两个例外。<1>一个大的子系统,在include下有自己的目录;<2>特定架构,头文件放置在arch/$(ARCH)/include/。
不同情况,头文件的使用
- kernel源码头文件使用
源码种include/linux/下的头文件使用,编译时gcc会自动寻找
- 外部模块头文件单独目录
外部模块的头文件单独目录放置,编译脚本中需要告诉kbuild编译系统,可以通过ccflags-y或CFLAGS_.o方式。
例如:
obj-m := 8123.o
ccflags-y := -Iinclude
8123-y := 8123_if.o 8123_pci.o 8123_bin.o
注意: 在-I和path之间一定不能有空格。
- 外部模块源码多目录放置
kbuild编译系统支持模块源码放置在多个目录,例如目下目录结构
.
|__ src
| |__ complex_main.c
| |__ hal
| |__ hardwareif.c
| |__ include
| |__ hardwareif.h
|__ include |__ complex.h
编译complex.ko,编译文件内容指定源文件需如下方式:
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
其中,二级制文件使用相对路径,这种方式不推荐使用。头文件中的$(src)一定要使用绝对路径。
五、编译安装目录(运行时编译)
概述
linux系统中,模块默认安装在
/lib/modules/$(KERNELRELEASE)/kernel/
外部模块默认安装在
/lib/modules/$(KERNELRELEASE)/extra/
INSTALL_MOD_PATH
改变默认安装目录的前置路径,编译时设置INSTALL_MOD_PATH即可。如下
make INSTALL_MOD_PATH=/frodo modules_install
把默认安装路径/lib/modules/$(KERNELRELEASE)/kernel/更改为 /frodo/lib/modules/$(KERNELRELEASE)/kernel/
该属性对编译kernel结构内或结构外模块,都可以使用。
INSTALL_MOD_DIR
改变模块的默认安装目录,编译时设置INSTALL_MOD_DIR即可。如
$ make INSTALL_MOD_DIR=gandalf -C $KDIR \ M=$PWD modules_install
把默认安装目录/lib/modules/$(KERNELRELEASE)/extra/更改为/lib/modules/$(KERNELRELEASE)/extra/。
六、版本控制
待补充,和模块接口的符号链接有关 。。。
七、小技巧
kernel中编译,在.config中指定模块是否编译。外部模块也可以使用这种方式,在.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