Ubuntu 驱动开发入门:编写一个简单的 "Hello World" 内核模块

在 Ubuntu 上开发驱动程序意味着要编写 Linux 内核模块。下面我将带你创建一个最简单的 "Hello World" 内核模块,并演示如何编译、加载和卸载它。

1. 准备工作

首先安装必要的开发工具和内核头文件:

bash
 
复制
 
下载
sudo apt update
sudo apt install build-essential linux-headers-$(uname -r)

2. 编写最简单的内核模块

创建一个名为 hello_world.c 的文件:

c
 
复制
 
下载
#include <linux/init.h>   // 包含模块初始化和清理函数的宏
#include <linux/module.h> // 包含内核模块相关的函数和宏
#include <linux/kernel.h> // 包含 printk 等内核函数

// 模块信息(可选但推荐)
MODULE_LICENSE("GPL");            // 许可证
MODULE_AUTHOR("Your Name");       // 作者
MODULE_DESCRIPTION("A simple Hello World Linux kernel module"); // 描述
MODULE_VERSION("0.1");            // 版本

// 模块初始化函数
static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, World from the Linux kernel!\n");
    return 0;    // 返回 0 表示成功
}

// 模块清理函数
static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye, World from the Linux kernel!\n");
}

// 注册模块的初始化和清理函数
module_init(hello_init);
module_exit(hello_exit);

3. 创建 Makefile

在同一目录下创建 Makefile 文件:

makefile
 
复制
 
下载
obj-m := hello_world.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

4. 编译模块

在终端中运行:

bash
 
复制
 
下载
make

如果一切顺利,你会看到生成了几个文件,其中最重要的是 hello_world.ko(内核对象文件)。

5. 加载和测试模块

加载模块:

bash
 
复制
 
下载
sudo insmod hello_world.ko

检查模块是否加载:

bash
 
复制
 
下载
lsmod | grep hello_world

查看内核日志输出:

bash
 
复制
 
下载
dmesg | tail -n 2

你应该能看到类似这样的输出:

text
 
复制
 
下载
[timestamp] Hello, World from the Linux kernel!

卸载模块:

bash
 
复制
 
下载
sudo rmmod hello_world

再次检查内核日志:

bash
 
复制
 
下载
dmesg | tail -n 2

现在应该能看到:

text
 
复制
 
下载
[timestamp] Goodbye, World from the Linux kernel!

6. 进阶说明

  1. printk 和日志级别:

    • KERN_INFO 是日志级别,其他级别包括 KERN_DEBUGKERN_WARNINGKERN_ERR 等

    • 查看所有内核消息:dmesg 或 journalctl -k

  2. 模块参数:
    可以添加可配置参数:

    c
     
    复制
     
    下载
    static char *name = "World";
    module_param(name, charp, 0644);
    MODULE_PARM_DESC(name, "The name to greet");
    
    static int __init hello_init(void)
    {
        printk(KERN_INFO "Hello, %s from the Linux kernel!\n", name);
        return 0;
    }

    然后加载时指定参数:

    bash
     
    复制
     
    下载
    sudo insmod hello_world.ko name="Linux"
  3. 安全性:

    • 内核模块运行在内核空间,错误会导致系统崩溃

    • 开发时建议在虚拟机中进行

  4. 调试:

    • 使用 printk 是最简单的调试方式

    • 更高级的调试可以使用 kgdb 或 kprobes

7. 清理

完成后可以清理编译生成的文件:

bash
 
复制
 
下载
make clean

下一步

如果你想进一步学习 Linux 驱动开发,可以:

  1. 学习字符设备驱动(创建 /dev 下的设备文件)

  2. 研究 file_operations 结构体实现读写操作

  3. 探索平台设备驱动和设备树(用于嵌入式开发)

这个简单的 "Hello World" 模块展示了最基本的 Linux 内核模块结构,是进入 Linux 驱动开发世界的第一步。

 
obj-m := hello_world.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) clean 解释下这个makefile的命令。
 
 

这个 Makefile 是用于编译 Linux 内核模块的标准模板。我来逐行解释它的含义和作用:


1. obj-m := hello_world.o

  • 作用:告诉内核构建系统要编译哪些模块。

  • 解释:

    • obj-m 表示要编译成可加载内核模块(.ko 文件)。

    • hello_world.o 是目标文件名(对应你的 .c 文件)。

    • 构建系统会自动将 hello_world.o 链接成 hello_world.ko


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

  • 作用:指定当前运行的内核的构建目录。

  • 解释:

    • $(shell uname -r) 执行 shell 命令获取当前内核版本(如 5.15.0-76-generic)。

    • /lib/modules/$(uname -r)/build 是标准的内核头文件和构建系统路径。

    • 这是内核模块编译时必须指向的目录,因为它包含内核的配置和头文件。


3. PWD := $(shell pwd)

  • 作用:获取当前工作目录的绝对路径。

  • 解释:

    • $(shell pwd) 执行 shell 的 pwd 命令,返回当前目录路径。

    • 用于告诉内核构建系统在哪里查找你的模块源代码。


4. all: 目标

makefile
 
复制
 
下载
all:
    $(MAKE) -C $(KDIR) M=$(PWD) modules
  • 作用:编译模块的主要指令。

  • 解释:

    • $(MAKE):调用 make 工具。

    • -C $(KDIR):切换到内核构建目录(/lib/modules/.../build)。

    • M=$(PWD):告诉内核构建系统模块源代码的位置(当前目录)。

    • modules:内核构建系统的目标,表示编译外部模块。

    • 实际效果:
      内核的构建系统会读取当前目录的代码,使用内核的配置和编译器设置来生成 .ko 文件。


5. clean: 目标

makefile
 
复制
 
下载
clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean
  • 作用:清理编译生成的文件。

  • 解释:

    • clean:内核构建系统的目标,表示清理生成的文件。

    • 会删除 .ko.o.mod.c 等中间文件。


关键点总结

部分作用
obj-m 指定要编译的模块目标(.o 文件)。
KDIR 指向当前运行内核的构建目录(包含内核头文件和配置)。
M=$(PWD) 告诉内核构建系统模块源代码的位置。
$(MAKE) -C ... 切换到内核目录并调用内核的构建系统处理当前目录的模块代码。
modules/clean 内核构建系统的目标,分别表示编译模块和清理生成文件。

为什么需要这样的 Makefile?

  1. 依赖内核构建系统:
    Linux 内核模块必须与当前运行的内核严格匹配(版本、配置、编译器选项等)。直接使用内核的构建系统可以确保兼容性。

  2. 简化编译过程:
    手动指定所有编译选项(如 -I 路径、宏定义等)非常复杂,而内核的构建系统自动处理这些细节。

  3. 标准化的模块构建:
    这是内核官方推荐的模块编译方式,所有 Linux 驱动开发者都使用类似的 Makefile。


实际执行流程示例

  1. 当你在终端运行 make 时:

    • Makefile 调用 $(MAKE) -C /lib/modules/$(uname -r)/build M=$(pwd) modules

    • 内核构建系统读取当前目录的代码,生成 hello_world.ko

  2. 运行 make clean 时:

    • 删除所有生成的文件,只保留源代码。


常见问题

Q1: 如果遇到 Cannot find /lib/modules/.../build 错误?

  • 原因:未安装当前内核的头文件。

  • 解决:

    bash
     
    复制
     
    下载
    sudo apt install linux-headers-$(uname -r)

Q2: 能否直接使用 gcc 编译内核模块?

  • 不能。内核模块需要特殊的内核头文件、编译器选项和链接方式,必须通过内核构建系统完成。

Q3: M=$(PWD) 的作用是什么?

  • 它向内核构建系统传递你的模块源代码路径,否则构建系统不知道在哪里找你的 .c 文件。


这个 Makefile 是 Linux 内核模块开发的标准模板,理解它的工作原理对驱动开发至关重要!

 
posted @ 2025-06-13 23:39  MaxBruce  阅读(344)  评论(0)    收藏  举报