内核模块 动态编译

不同目录编译(动态编译):out-of-tree

基于内核的框架,需事先编译好内核(依赖文件 Module.symvers、vmlinux)。模块开发大多使用此方法。
新目录包含驱动源码,用特定格式 Makefile 编译。
将生成的 .ko 文件拷贝到要安装的Linux环境。
使用 insmod 安装驱动插件。
模块 Makefile 根据指定内核配置文件编译。

Makefile 说明

/Documentation/kbuild/modules.txt
/Documentation/kbuild/makefiles.txt

Makefile 变量:
    __KERNEL__  :表示编译入内核。
    KERNELRELEASE:内核版本号。
    MODULE:表示编译成模块。
    EXTRA_FLAGS:额外的外部变量,不会覆盖内部。
    KBUILD_EXTRA_SYMBOLS:模块的符号表文件。通常为编译模块依赖的模块目录下的 Module.symvers
        如果多个相互依赖的模块在一个 Makefile 中编译,则可省略。被依赖而模块放在前。
    xxx/linux/version.h:版本定义头文件。
    /lib/modules/$(shell uname -r)/build:本系统内核构建源码的构建文件。
    解决报错:apt install linux-headers-$(uname -r)
    与 arm 区分:ifeq ($(ARCH),arm)

Makefile 解析

模块 Makefile 核心思想:-C 进入内核路径,获取内核信息(编译器、架构等);M 指定被编译模块的目录
即依赖进入的内核目录,执行当前目录下的 Makefile 编译 modules。
    $(MAKE) -C <KERNELDIR>/ M=`pwd` modules
    $(MAKE) -C <KERNELDIR>/ M=`pwd` modules clean
    // $(MAKE) -C <KERNELDIR>/ M=`pwd` modules_install
    // $(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(path) modules_install
---------------------------------------
解决重复读取变量:
    make -C 读入内核 Makefile 并传入本 Makefile 地址做参数,会引起两次读取
    使用如下语句做区分:(Makefile 详见附件)
    ifneq ($(KERNELRELEASE),)
    else
    endif
---------------------------------------
驱动模块多文件编译 + 多模块编译:TARGET 不能与任何依赖重名。
    TARGET = <module_name1>
    obj-m += $(TARGET).o
    $(TARGET)-objs := a.o b.o ...
    obj-m += <module_name2>.o
    <module_name2>-objs += b.o c.o ...
---------------------------------------
引用其他模块符号:如果多个相互依赖的模块在一个 Makefile 中编译,则可省略。被依赖而模块放在前。
    KBUILD_EXTRA_SYMBOLS += <module1_path>/Module.symvers
                         += <module2_path>/Module.symvers
                         ... ...
    export KBUILD_EXTRA_SYMBOLS

Kbuild 与 Makefile

新版本内核在编译时会先从 Kbuild 中寻找目标和依赖,然后再去 Makefile 中寻找。
即可以将 ifneq ($(KERNELRELEASE),) 中的内容放入和 Makefile 同级目录的 Kbuild 中。
为保证兼容,ifneq ($(KERNELRELEASE),) 下可以直接只包含 Kbuild(include Kbuild)。

Linux内核模块编译、加载、运行机制分析、版本控制、许可声明、内核污染、模块传参、模块签名机制、out-of-tree动态模块编译及Makefile模板编写,尽在《Linux内核编程》,详情点击:王利涛老师个人淘宝店:Linux内核编程