Linux 驱动开发之内核模块分析2

Linux 驱动开发之内核模块分析2(基于Linux6.6)---内核模块编译 Makefile分析

一、模块的编译

在 Linux 驱动开发中,驱动编译主要分为静态编译动态编译两种方式。这两种编译方式在内核模块的加载方式、依赖关系、更新机制等方面有所不同,适用于不同的场景。

1. 静态编译(Static Compilation)

静态编译指的是将驱动代码直接编译到内核映像中。这样,驱动在内核启动时就已经被包含,不需要单独的模块加载或卸载过程。

特点:

  • 编译过程:驱动的源代码被直接编译到内核映像中,在内核构建时就被静态链接到内核中。
  • 无需模块加载:一旦内核启动,驱动程序即加载并运行,不需要用户手动加载或卸载模块。
  • 内核与驱动绑定:驱动代码和内核紧密结合,更新驱动需要重新编译内核。
  • 不支持热插拔:无法在系统运行时动态加载或卸载驱动。

使用场景:

  • 嵌入式设备:对于资源有限、需要较高稳定性的嵌入式系统,通常会选择静态编译,以减少内存使用和启动时间。
  • 高安全性要求:在某些安全性要求较高的环境中,静态编译可以避免动态加载模块带来的潜在安全风险。

编译步骤:

  1. 在内核的 .config 文件中启用相应的驱动选项,通常是 CONFIG_<driver_name>=y
  2. 重新编译内核并安装。
  3. 驱动将作为内核的一部分加载。

例如,假设我们有一个网卡驱动 eth_driver,我们在 .config 文件中启用它:

CONFIG_NET_VENDOR_XYZ=y
CONFIG_ETH_DRIVER=y

然后重新编译内核:

make && make install

此时,eth_driver 驱动就会被静态编译进内核。

2. 动态编译(Dynamic Compilation)

动态编译(通常指的是模块化编译)指的是将驱动代码编译为独立的内核模块,这些模块可以在运行时动态加载和卸载。

特点:

  • 编译过程:驱动程序被编译为一个或多个内核模块(.ko 文件),并在需要时动态加载到内核中。
  • 动态加载与卸载:模块可以在系统运行时通过 insmod(加载)和 rmmod(卸载)命令进行加载和卸载。也可以使用 modprobe 管理模块依赖。
  • 节省内存:仅加载需要的驱动模块,未使用的驱动不会占用系统资源。
  • 支持热插拔:在运行时可以根据需要动态加载驱动程序,例如插入一个新硬件设备时,自动加载相应的驱动。

使用场景:

  • 硬件支持:对于需要支持多种硬件设备的系统,使用动态编译可以根据实际硬件情况加载对应的驱动。
  • 模块更新:开发过程中,如果需要更新驱动程序,只需重新编译模块并加载,而不需要重新编译整个内核。
  • 驱动调试:对于驱动开发人员,动态编译和加载模块便于调试和测试,不需要每次修改后都重新编译内核。

编译步骤:

  1. 在内核的 .config 文件中启用相应的驱动选项,通常是 CONFIG_<driver_name>=m
  2. 使用 make modules 编译内核模块。
  3. 使用 insmodmodprobe 加载模块。

例如,假设我们有一个网络驱动 eth_driver,我们在 .config 文件中启用它作为模块:

CONFIG_NET_VENDOR_XYZ=m
CONFIG_ETH_DRIVER=m

然后编译内核模块:

make modules

生成的内核模块(eth_driver.ko)可以通过以下命令加载:

sudo insmod eth_driver.ko

或者通过 modprobe 加载,并自动解析模块依赖:

sudo modprobe eth_driver

3. 静态编译 vs 动态编译

特性静态编译动态编译
编译方式驱动被直接编译进内核映像驱动编译为独立的模块(.ko
加载方式驱动随内核一起加载驱动可以动态加载和卸载
内存占用总是占用内存,不能按需加载仅加载需要的模块,占用较少内存
更新驱动更新驱动需要重新编译整个内核可以单独编译并加载更新模块
热插拔支持不支持支持热插拔,支持在运行时加载模块
使用场景嵌入式系统、高安全性需求大型系统、多硬件支持、驱动开发

二、具体编译过程分析   

对于一个普通的linux设备驱动模块,以下是一个经典的makefile代码,使用下面这个makefile可以完成大部分驱动的编译,使用时只需要修改一下要编译生成的驱动名称即可。只需修改obj-m的值。

ifneq  ($(KERNELRELEASE),)

obj-m:=hello.o

else

KDIR := /lib/modules/$(shell uname -r)/build

PWD:=$(shell pwd)

all:

    make -C $(KDIR) M=$(PWD) modules

clean:

    rm -f *.ko *.o *.symvers *.cmd *.cmd.o

endif

2.1、makefile 中的变量

    先说明以下makefile中一些变量意义:

(1)KERNELRELEASE           在linux内核源代码中的顶层makefile中有定义

(2)shell pwd                             取得当前工作路径

(3)shell uname -r                    取得当前内核的版本号

(4)KDIR                                     当前内核的源代码目录。

关于linux源码的目录有两个,分别为

 "/lib/modules/$(shell uname -r)/build"

"/usr/src/linux-header-$(shell uname -r)/"

       但如果编译过内核就会知道,usr目录下那个源代码一般是我们自己下载后解压的,而lib目录下的则是在编译时自动copy过去的,两者的文件结构完全一样,因此有时也将内核源码目录设置成/usr/src/linux-header-$(shell uname -r)/。关于内核源码目录可以根据自己的存放位置进行修改。

(5)make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules

这就是编译模块了:

a -- 首先改变目录到-C选项指定的位置(即内核源代码目录),其中保存有内核的顶层makefile;

b -- M=选项让该makefile在构造modules目标之前返回到模块源代码目录;然后,modueles目标指向obj-m变量中设定的模块;在上面的例子中,我们将该变量设置成了hello.o。

2.2、make 的的执行步骤

a -- 第一次进来的时候,宏“KERNELRELEASE”未定义,因此进入 else;

b -- 记录内核路径,记录当前路径;

       由于make 后面没有目标,所以make会在Makefile中的第一个不是以.开头的目标作为默认的目标执行。默认执行all这个规则

c -- make -C $(KDIR) M=$(PWD) modules

     -C 进入到内核的目录执行Makefile ,在执行的时候KERNELRELEASE就会被赋值,M=$(PWD)表示返回当前目录,再次执行makefile,modules 编译成模块的意思

     所以这里实际运行的是

     make -C /lib/modules/2.6.13-study/build M=/home/fs/code/1/module/hello/ modules

d -- 再次执行该makefile,KERNELRELEASE就有值了,就会执行obj-m:=hello.o

     obj-m:表示把hello.o 和其他的目标文件链接成hello.ko模块文件,编译的时候还要先把hello.c编译成hello.o文件

可以看出make在这里一共调用了3次

   1)-- make
   2)-- linux内核源码树的顶层makedile调用,产生。o文件
   3)-- linux内核源码树makefile调用,把.o文件链接成ko文件

2.3、编译多文件

若有多个源文件,则采用如下方法:

obj-m := hello.o

hello-objs := file1.o file2.o file3.o

三、内部编译简单说明

  

在 Linux 内核开发中,Makefile 用于管理和自动化编译过程。内核源码树的根目录和各个子目录都包含 Makefile,用于控制源代码的编译过程。在这里,我将通过一个简单的实例来介绍如何编写和应用一个 Linux 内核模块的 Makefile。

1. 简单的内核模块结构

假设我们要编写一个简单的 Linux 内核模块 hello.c,这个模块会在加载时打印一条消息,在卸载时打印另一条消息。目录结构如下:

/home/user/kernel_module/
├── Makefile
└── hello.c

其中 hello.c 是内核模块源代码,Makefile 用于编译该模块。

2. 编写内核模块源代码 (hello.c)

首先,我们创建一个简单的内核模块,代码如下:

// hello.c
#include <linux/module.h>  // 所需的头文件
#include <linux/kernel.h>
#include <linux/init.h>

// 模块初始化函数
static int __init hello_init(void) {
    printk(KERN_INFO "Hello, world!\n");
    return 0;
}

// 模块退出函数
static void __exit hello_exit(void) {
    printk(KERN_INFO "Goodbye, world!\n");
}

// 指定模块的初始化和退出函数
module_init(hello_init);
module_exit(hello_exit);

// 模块信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Hello World Kernel Module");

这个模块包含两个基本的函数:

  • hello_init():模块加载时执行。
  • hello_exit():模块卸载时执行。

3. 编写 Makefile

接下来,我们为这个模块编写一个简单的 Makefile。Makefile 的作用是告诉 make 工具如何编译源文件以及生成最终的内核模块。

# Makefile

# 内核源代码路径。这里假设你在开发模块时的环境下,内核源码已被安装。
KERNEL_DIR ?= /lib/modules/$(shell uname -r)/build

# 当前目录
PWD := $(shell pwd)

# 模块的源文件
obj-m := hello.o

# 默认目标
all:
	make -C $(KERNEL_DIR) M=$(PWD) modules

# 清理目标
clean:
	make -C $(KERNEL_DIR) M=$(PWD) clean

Makefile 解释

  1. KERNEL_DIR:指定内核源代码的路径。使用 $(shell uname -r) 获取当前运行内核的版本,并从该版本的路径中获取 build 目录。

    • $(KERNEL_DIR) 会指向你的内核源码目录,通常在 /lib/modules/$(kernel_version)/build 位置。
  2. obj-m:这是一个标准的 Makefile 变量,用于指定内核模块对象文件。obj-m := hello.o 表示我们要编译 hello.o 对象文件,最终会生成一个名为 hello.ko 的内核模块。

  3. all 目标:这是默认的目标,make 命令执行时会自动调用它。这个目标会调用内核的 make 系统来编译模块。make -C $(KERNEL_DIR) M=$(PWD) modules 命令的作用是:

    • -C $(KERNEL_DIR)make 切换到内核源码目录。
    • M=$(PWD) 告诉 make 当前目录是内核模块的目录,确保它知道在哪里找到 hello.c 文件。
    • modules 是编译模块的目标。
  4. clean 目标:当你想要清理编译过程中的临时文件时,执行 make clean。这个目标会调用 make -C $(KERNEL_DIR) M=$(PWD) clean 来清理模块相关的文件。

4. 编译模块

在终端中进入 kernel_module 目录,执行以下命令来编译内核模块:

make

这个命令会使用 Makefile 中的规则,将 hello.c 编译成 hello.ko 内核模块文件。

如果编译成功,你会看到类似以下的输出:

make -C /lib/modules/5.4.0-42-generic/build M=/home/user/kernel_module modules
make[1]: Entering directory '/usr/src/linux-headers-5.4.0-42-generic'
  CC [M]  /home/user/kernel_module/hello.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/user/kernel_module/hello.mod.o
  LD [M]  /home/user/kernel_module/hello.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.4.0-42-generic'

生成的 hello.ko 就是你的内核模块文件。

5. 加载和卸载模块

你可以通过以下命令来加载和卸载模块:

  • 加载模块

  • sudo insmod hello.ko
    

    加载后,内核会打印 Hello, world! 消息。

  • 卸载模块

  • sudo rmmod hello
    

    卸载后,内核会打印 Goodbye, world! 消息。

  • 查看内核日志: 可以使用 dmesg 命令查看内核日志输出,确认模块的加载和卸载信息:

  • dmesg | tail
    

6. 清理构建文件

如果你想要清理编译生成的文件,可以运行:

make clean

这将删除所有编译生成的临时文件,包括 hello.ohello.ko

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值