【内核驱动基础】内核模块的两种编译方式(in-tree vs out-of-tree)

目录

[一、内核模块的编译方式(in-tree vs out-of-tree)](#一、内核模块的编译方式(in-tree vs out-of-tree))

二、内核树外编译(out-of-tree)

特点

典型做法

适用场景

三、内核树内编译(in-tree)

参考资料


一、内核模块的编译方式(in-tree vs out-of-tree)

通常而言,内核模块代码有两种常见的构建形态:

  1. 内核树内编译(in-tree):把模块源码放进(或集成进)内核源码树,由内核顶层构建系统统一编译。
  2. 内核树外编译(out-of-tree,外部模块) :模块源码独立放在内核树之外,通过 make -C <kernel_dir> M=<module_dir> modules 借用内核 Kbuild 来编译。

二、内核树外编译(out-of-tree)

特点

  • 模块源码独立,不需要进入内核源码树。

  • 仍然使用内核的 Kbuild 规则来编译,关键命令是:

    复制代码
    make -C <KERNEL_DIR> M=<MODULE_DIR> modules

典型做法

  • 模块目录里只放一个"外部模块 Makefile"
    • obj-m := hellomodule.o
    • make -C $(KERNEL_DIR) M=$(CURDIR) modules

适用场景

  • 驱动/模块开发调试(迭代快)
  • 第三方闭源或单独交付(不改内核树)
  • 同一内核版本下给多个项目复用

在内核树外编译的方式在我们之前的文章中其实已经实现了,可以参考【内核驱动基础】超详细一文详解Linux驱动模块这一文章,主要实现就是Makefile文件的编写

三、内核树内编译(in-tree)

在内核源码的文件目录下,我们可以看到有很多的驱动程序,我们通过drivers/char中的Makefile文件进一步举例说明,其位置如下:

Makefile文件的内容如下:

复制代码
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the kernel character device drivers.
#

obj-y				+= mem.o random.o
obj-$(CONFIG_TTY_PRINTK)	+= ttyprintk.o
obj-y				+= misc.o
obj-$(CONFIG_ATARI_DSP56K)	+= dsp56k.o
obj-$(CONFIG_VIRTIO_CONSOLE)	+= virtio_console.o
obj-$(CONFIG_RAW_DRIVER)	+= raw.o
obj-$(CONFIG_MSPEC)		+= mspec.o
obj-$(CONFIG_UV_MMTIMER)	+= uv_mmtimer.o
obj-$(CONFIG_IBM_BSR)		+= bsr.o

obj-$(CONFIG_PRINTER)		+= lp.o

obj-$(CONFIG_APM_EMULATION)	+= apm-emulation.o

obj-$(CONFIG_DTLK)		+= dtlk.o
obj-$(CONFIG_APPLICOM)		+= applicom.o
obj-$(CONFIG_SONYPI)		+= sonypi.o
obj-$(CONFIG_HPET)		+= hpet.o
obj-$(CONFIG_XILINX_HWICAP)	+= xilinx_hwicap/
obj-$(CONFIG_NVRAM)		+= nvram.o
obj-$(CONFIG_TOSHIBA)		+= toshiba.o
obj-$(CONFIG_DS1620)		+= ds1620.o
obj-$(CONFIG_HW_RANDOM)		+= hw_random/
obj-$(CONFIG_PPDEV)		+= ppdev.o
obj-$(CONFIG_NWBUTTON)		+= nwbutton.o
obj-$(CONFIG_NWFLASH)		+= nwflash.o
obj-$(CONFIG_SCx200_GPIO)	+= scx200_gpio.o
obj-$(CONFIG_PC8736x_GPIO)	+= pc8736x_gpio.o
obj-$(CONFIG_NSC_GPIO)		+= nsc_gpio.o
obj-$(CONFIG_GPIO_TB0219)	+= tb0219.o
obj-$(CONFIG_TELCLOCK)		+= tlclk.o

obj-$(CONFIG_MWAVE)		+= mwave/
obj-y				+= agp/
obj-$(CONFIG_PCMCIA)		+= pcmcia/

obj-$(CONFIG_HANGCHECK_TIMER)	+= hangcheck-timer.o
obj-$(CONFIG_TCG_TPM)		+= tpm/

obj-$(CONFIG_PS3_FLASH)		+= ps3flash.o

obj-$(CONFIG_XILLYBUS)		+= xillybus/
obj-$(CONFIG_POWERNV_OP_PANEL)	+= powernv-op-panel.o
obj-$(CONFIG_ADI)		+= adi.o

这个文件的主要作用就是告诉内核的构建系统(Kbuild)在"字符设备驱动"这个目录下,需要编译哪些源文件/子目录,以及它们是编进内核(built-in)还是编成模块(.ko)。

典型写法是这种:

复制代码
obj-$(CONFIG_FOO) += foo.o

含义:

  • CONFIG_FOO=yfoo.o 编进内核镜像(built-in)
  • CONFIG_FOO=mfoo.o 会被链接成 foo.ko(模块)
  • CONFIG_FOO=n → 不编译

那么问题来了,这些变量在哪里被设置呢?

---答:在内核根目录.config文件中进行配置,在我们make板卡厂家给的默认配置时,会默认配置.config文件

截取部分.config文件如下:

我们继续提问,这个.config又如何去修改里面的配置参数呢?当然,如果你要手动编写也是无可厚非的,但问题是内核有成千上万个配置选项(CONFIG_*),所以,内核引入了menuconfig。

这个.config文件是由各个驱动目录下的Kconfig文件汇总得到的,只需要修改驱动目录下对应的Kconfig文件,使用make menuconfig可以快速配置.config文件的参数内容。

menuconfig 是 Linux 内核 Kconfig 配置系统提供的一个交互式(终端界面)配置工具。它的核心目的很简单:让你用"菜单"的方式生成/修改内核的 .config,从而决定内核和各类驱动/子系统到底编译进内核(y)、编译成模块(m)还是不编译(n)。其界面如下:

在执行make menuconfig指令的时候一定要确定好自己的编译平台和交叉编译链工具是否正确,否则在部署到板卡上后很有可能会产生错误。例如在rk3588上是如下配置的:

复制代码
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig

menuconfig常用的快捷键如下

  • /:搜索配置项(强烈推荐)
    • 输入关键字或 CONFIG_ 名称,能直接定位选项所在菜单
  • ?:查看当前选项帮助信息(help)
  • y / n / m:直接把当前项设为 y/n/m
  • Space:循环切换状态(n → m → y)
  • Esc:返回/退出

技巧:找不到某个驱动选项时,先按 / 搜它,再根据搜索结果跳转到对应菜单。

下面我们使用一个实例进行强化:

我们当前完成了一个简单驱动程序模块:

cpp 复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>

static int __init hello_init(void)
{
    printk(KERN_EMERG "[ KERN_EMERG ]  Hello  Module Init\\n");
    printk( "[ default ]  Hello  Module Init\\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk("[ default ]   Hello  Module Exit\\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL2");
MODULE_AUTHOR("cc ");
MODULE_DESCRIPTION("hello world module");
MODULE_ALIAS("test_module");

我们将程序放入内核drivers/char目录下

在当前目录下的Makefile文件中,我们配置模块参数

在当前目录的Kconfig文件中,编写模块的Kconfig信息

此处的状态可以设置为bool(y或n)或者tristate(三态,y、n、m,m表示为一个模块)

然后在内核根目录执行make menuconfig,使用/搜索HELLO_MODULE可以看到如下:

在.config文件中搜索HELLO_MODULE可以看到参数已经配置成y

如果想要配置成默认为n,也可以在menuconfig中配置

至此,介绍完了两种编译方式,大家可以自行尝试加深理解。

参考资料

相关推荐
j_xxx404_2 小时前
Linux:进程
linux·运维·服务器
小虾爬滑丫爬2 小时前
Debian服务器上重启服务
linux·服务重启
wheeldown2 小时前
【Linux TCP Socket 实战】 从单客户端到多客户端回声服务器
linux·服务器·tcp/ip
m0_748244962 小时前
Linux C++项目推荐:文件服务器+如何快速上手
linux·服务器·c++
looking_for__2 小时前
【Linux】网络基础
linux·服务器·网络
似霰2 小时前
Linux Shell 脚本编程——脚本自动化基础
linux·自动化·shell
南棱笑笑生2 小时前
20260127让天启AIO-3576Q38开发板跑Rockchip瑞芯微原厂的Buildroot【linux-6.1内核】【使用天启Firefly的DTS】
linux·运维·elasticsearch·rockchip
玉梅小洋2 小时前
Linux中 cd命令进入以 - 开头的目录报错及解决方法
linux·运维·服务器
努力努力再努力wz2 小时前
【Linux网络系列】:打破 HTTP 明文诅咒,在Linux 下用 C++ 手搓 HTTPS 服务器全过程!(附实现源码)
linux·服务器·网络·数据结构·c++·http·https