从零开始:在Ubuntu 22.04上编译你的第一个Linux内核模块(附常见错误排查)

1. 环境准备与工具链配置

在Ubuntu 22.04上开发内核模块,首先需要确保开发环境完整。与普通应用程序开发不同,内核模块编译需要特殊的内核头文件和构建工具链。以下是必备组件:

sudo apt update
sudo apt install build-essential linux-headers-$(uname -r) libelf-dev flex bison

关键点验证:

  • 检查gcc版本是否匹配内核编译环境:
    gcc --version
    uname -r
    
  • 确认内核头文件路径存在:
    ls /lib/modules/$(uname -r)/build
    

常见问题解决方案:

  1. gcc版本不匹配:Ubuntu 22.04默认使用gcc-11,但内核可能用gcc-12编译
    sudo apt install gcc-12
    sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 100
    
  2. 缺失cc符号链接:
    sudo ln -sf /usr/bin/gcc /usr/bin/cc
    

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

创建一个hello_world.c文件,包含以下基本结构:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Linux kernel module");

static int __init hello_init(void) {
    printk(KERN_INFO "Hello world from kernel space!\n");
    return 0;
}

static void __exit hello_exit(void) {
    printk(KERN_INFO "Goodbye from kernel space!\n");
}

module_init(hello_init);
module_exit(hello_exit);

代码解析:

  • module_init/module_exit:注册模块加载/卸载时的回调函数
  • printk:内核态打印函数(输出到内核日志,非终端)
  • MODULE_*宏:定义模块元信息(必须包含许可证声明)

3. 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

编译执行:

make

成功后会生成hello_world.ko文件(内核对象模块)

Makefile关键参数说明:

参数作用
obj-m指定要编译的模块目标文件
KDIR指向内核构建目录
M=$(PWD)指定模块源代码所在目录
modules内核构建系统的模块编译目标

4. 模块加载与调试实战

加载模块:

sudo insmod hello_world.ko

验证模块状态:

lsmod | grep hello_world

查看内核日志:

dmesg | tail -n 2
# 或使用更专业的日志工具
journalctl -k --since "1 minute ago"

卸载模块:

sudo rmmod hello_world

常见错误排查:

  1. 版本不匹配错误:

    disagrees about version of symbol module_layout
    

    解决方案:确保linux-headers版本与uname -r完全一致

  2. printk无输出:

    • 检查日志级别:cat /proc/sys/kernel/printk
    • 使用dmesg -l info过滤特定级别日志
  3. 模块加载失败:

    sudo modprobe --dump-modversions /lib/modules/$(uname -r)/build/Module.symvers
    

5. 高级调试技巧

打印调试增强:

// 添加调试宏
#define pr_debug(fmt, ...) \
    printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)

// 启用动态调试
#include <linux/dynamic_debug.h>
module_param(dyndbg, charp, 0644);
MODULE_PARM_DESC(dyndbg, "Enable dynamic debug");

GDB调试配置:

  1. 安装调试符号包:
    sudo apt install linux-image-$(uname -r)-dbgsym
    
  2. 加载符号:
    gdb vmlinux /proc/kcore
    

性能分析工具:

  • perf probe:动态跟踪内核函数
  • ftrace:内核函数调用跟踪
    echo function > /sys/kernel/debug/tracing/current_tracer
    cat /sys/kernel/debug/tracing/trace_pipe
    

6. 生产环境注意事项

安全规范:

  • 模块签名验证(防止恶意模块加载)
    openssl req -new -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -nodes
    perl -pe 's/[\r\n]//g' cert.pem | awk '{print "0x"$0}' | tr -d '\n' > cert.hex
    
  • 在Makefile中添加:
    CONFIG_MODULE_SIG=y
    CONFIG_MODULE_SIG_ALL=y
    

稳定性建议:

  1. 内存管理:

    • 使用kmalloc/kfree而非标准库函数
    • 注意GFP标志选择(GFP_KERNEL vs GFP_ATOMIC)
  2. 错误处理:

    if (!ptr) {
        pr_err("Memory allocation failed\n");
        return -ENOMEM;
    }
    
  3. 并发控制:

    #include <linux/spinlock.h>
    static DEFINE_SPINLOCK(my_lock);
    
    spin_lock(&my_lock);
    // 临界区代码
    spin_unlock(&my_lock);
    

7. 从Hello World到实际驱动

字符设备驱动框架:

#include <linux/fs.h>

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = device_read,
    .write = device_write,
    .open = device_open,
    .release = device_release
};

static int __init dev_init(void) {
    register_chrdev(MAJOR_NUM, "my_device", &fops);
    // 创建设备节点
    class_create(THIS_MODULE, "my_class");
    device_create(cls, NULL, dev, NULL, "my_device");
    return 0;
}

典型开发流程:

  1. 定义设备操作接口(file_operations)
  2. 注册主设备号
  3. 实现read/write/ioctl等回调
  4. 创建设备文件节点
  5. 添加用户空间通信机制(ioctl、sysfs等)

实用代码片段:

// 用户空间数据拷贝
copy_from_user(kernel_buf, user_buf, count);
copy_to_user(user_buf, kernel_buf, count);

// IOCTL接口示例
long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    switch(cmd) {
        case MY_CMD_1:
            // 处理命令1
            break;
        default:
            return -ENOTTY;
    }
    return 0;
}

8. 持续集成与自动化测试

内核模块单元测试框架:

#!/usr/bin/env python3
import subprocess
import unittest

class TestKernelModule(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        subprocess.run(["make"], check=True)
        subprocess.run(["sudo", "insmod", "hello.ko"], check=True)
    
    def test_module_loaded(self):
        result = subprocess.run(["lsmod"], capture_output=True, text=True)
        self.assertIn("hello", result.stdout)
    
    @classmethod
    def tearDownClass(cls):
        subprocess.run(["sudo", "rmmod", "hello"], check=True)
        subprocess.run(["make", "clean"])

if __name__ == "__main__":
    unittest.main()

CI/CD集成示例:

# .gitlab-ci.yml
stages:
  - test

kernel_module_test:
  stage: test
  image: ubuntu:22.04
  before_script:
    - apt update && apt install -y make gcc linux-headers-$(uname -r)
  script:
    - make
    - insmod hello_world.ko || true
    - dmesg | grep "Hello world"
    - rmmod hello_world || true
  tags:
    - docker
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐