编译kernel外部模块


翻译:kernel/Documents/kbuild/modules.txt
该篇文章介绍怎么编译kernel代码结构之外的模块

一、说明

“kbuild”是kernel的编译系统。模块使用kbuid编译,使kernel编译系统保持其兼容性,编译框架修改不影响模块的编译,编译系统确保模块使用gcc使用正确的编译标记。kernel编译提供kernel结构内和结构外编译功能,无论结构内还是结构外,模块编译的方式是类似的,最初的kernel 模块编译都是结构外编译方式。

该文档主要介绍kernel结构外编译外部模块,如何实现隐藏复杂的内部编译逻辑,仅简单实现,使用“make”就可以编译外部模块。

二、编译外部模块

条件

  1. 存在完整的头文件和配置文件且已预编译完成
  2. 已完全编译过的kernel

以上条件说明,完全编译过的kernel,删掉其中的源文件(即.c文件),也可以编译外部模块。当和第三方合作,不想暴露逻辑实现,有需要给对方提供kernel编译环境时,可以采用这种方式。分布式kernel编译在次不做说明

语法

  1. 编译命令

依赖源码编译
编译外部模块的命令是:

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
  1. 编译选项
make -C $KDIR M=$PWD
  • -C $DIR kernel源码路径,编译过程中会改变该路径下内容,编译完成后回退修改,还原到编译前
  • M=$PWD 外部模块的源码目录。M的值必须时绝对路径
  1. 编译目标

编译外部模块时,仅编译系统的几个编译目标可以被使用

make -C $KDIR M=$PWD [target]

编译外部模块,不会更新kernel源码,编译产生的文件在源码目录。编译使用make即可,不需要指定目标。

  • modules 外部模块的默认编译目标
  • modules_install 基于运行时kernel编译,编译完成后把编译目标安装到/lib/modules/<kernel_release>/extra/目录,或者INSTALL_MOD_PATH指定的目录。
  • clean 删除编译产生的文件和目录
  • help 列出编译出的模块
  1. 编译单个文件
    编译模块时,可以逐个编译模块的源文件,该种编译方式,编译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/。

不同情况,头文件的使用

  1. kernel源码头文件使用

源码种include/linux/下的头文件使用,编译时gcc会自动寻找

  1. 外部模块头文件单独目录

外部模块的头文件单独目录放置,编译脚本中需要告诉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之间一定不能有空格。

  1. 外部模块源码多目录放置

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
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值