深入理解 Linux 内核模块:核心知识、注册机制与文件系统映射

1. Linux 内核模块概述

1.1 什么是内核模块?

Linux 内核模块(Kernel Module)是 可动态加载和卸载的内核代码,主要用于:

  • 驱动开发(如字符设备驱动、块设备驱动、网络驱动)
  • 内核扩展(如文件系统、协议栈)
  • 性能优化(如内核内存管理模块)

相比将所有功能直接编译进内核,内核模块的优势
按需加载/卸载,节省内存
独立开发、调试,无需重启系统
降低耦合度,避免核心代码污染


在这里插入图片描述

2. 内核模块的基本框架

#include <linux/module.h>   // 内核模块头文件
#include <linux/init.h>     // __init 和 __exit 相关宏
#include <linux/kernel.h>   // printk 等

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A Simple Kernel Module");

// 模块加载函数
static int __init my_module_init(void)
{
    printk(KERN_INFO "My Kernel Module Loaded.\n");
    return 0;
}

// 模块卸载函数
static void __exit my_module_exit(void)
{
    printk(KERN_INFO "My Kernel Module Unloaded.\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

3. 设备号、设备类、设备文件:核心知识解析

3.1 设备号(Major & Minor Number)

在 Linux 中,每个设备都有唯一标识 设备号(Device Number),包括:

  • 主设备号(Major Number):标识设备类别(如磁盘、串口)
  • 次设备号(Minor Number):区分同类设备的不同实例
设备号注册方式
  1. 动态分配
dev_t dev_num;
alloc_chrdev_region(&dev_num, 0, 1, "my_device");
  1. 静态指定
#define MAJOR_NUM 240
dev_t dev_num = MKDEV(MAJOR_NUM, 0);
register_chrdev_region(dev_num, 1, "my_device");

⚠️ 建议使用动态分配,避免冲突。


3.2 设备类(Class)与 /dev 目录

Linux 通过 设备类(Class) 把设备组织起来,便于 udev 自动创建 /dev 设备文件。

步骤:

  1. 创建设备类
struct class *my_class;
my_class = class_create(THIS_MODULE, "my_class");
  1. 创建设备文件
struct device *my_device;
my_device = device_create(my_class, NULL, dev_num, NULL, "my_device");

加载后,ls /dev/my_device 应可看到设备文件。

  1. 卸载时删除
device_destroy(my_class, dev_num);
class_destroy(my_class);

3.3 在文件系统中的体现

device_create() 创建 /dev/my_device 时:

  • /dev/my_device 并不存储数据,而是 操作系统提供的接口
  • 当用户调用 open("/dev/my_device"),内核会查找该设备对应的 file_operations
  • read()write()ioctl() 等调用最终会映射到 设备驱动程序

文件系统的作用:通过 /dev 目录,让用户像操作文件一样访问硬件。


4. 字符设备驱动完整示例

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "mychardev"
static dev_t dev_num;
static struct cdev my_cdev;
static struct class *my_class;
static struct device *my_device;

// 打开设备
static int my_open(struct inode *inodep, struct file *filep) {
    printk(KERN_INFO "Device opened\n");
    return 0;
}

// 关闭设备
static int my_release(struct inode *inodep, struct file *filep) {
    printk(KERN_INFO "Device closed\n");
    return 0;
}

// 读取设备
static ssize_t my_read(struct file *filep, char __user *buffer, size_t len, loff_t *offset) {
    char msg[] = "Hello from kernel!\n";
    int msg_len = sizeof(msg);

    if (*offset >= msg_len) return 0;
    if (len > msg_len - *offset) len = msg_len - *offset;

    if (copy_to_user(buffer, msg + *offset, len)) return -EFAULT;
    *offset += len;

    return len;
}

// 文件操作
static struct file_operations fops = {
    .owner   = THIS_MODULE,
    .open    = my_open,
    .release = my_release,
    .read    = my_read,
};

// 初始化模块
static int __init my_module_init(void) {
    // 动态分配设备号
    if (alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME) < 0) {
        printk(KERN_ALERT "Failed to allocate device number\n");
        return -1;
    }

    // 创建类
    my_class = class_create(THIS_MODULE, "my_class");
    if (IS_ERR(my_class)) {
        unregister_chrdev_region(dev_num, 1);
        return PTR_ERR(my_class);
    }

    // 初始化字符设备
    cdev_init(&my_cdev, &fops);
    if (cdev_add(&my_cdev, dev_num, 1) < 0) {
        class_destroy(my_class);
        unregister_chrdev_region(dev_num, 1);
        return -1;
    }

    // 创建设备文件
    my_device = device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME);
    if (IS_ERR(my_device)) {
        cdev_del(&my_cdev);
        class_destroy(my_class);
        unregister_chrdev_region(dev_num, 1);
        return PTR_ERR(my_device);
    }

    printk(KERN_INFO "Char device registered\n");
    return 0;
}

// 卸载模块
static void __exit my_module_exit(void) {
    device_destroy(my_class, dev_num);
    class_destroy(my_class);
    cdev_del(&my_cdev);
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_INFO "Char device unregistered\n");
}

module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");

5. 面试高频问题

Q1: 什么是 cdev 结构体?

cdev 代表 字符设备结构体,用于注册文件操作(file_operations):

struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
cdev_add(&my_cdev, dev_num, 1);

Q2: device_create()/dev 中做了什么?

  • 它在 /sys/class/my_class/ 目录下创建 sysfs 设备节点
  • udev 监听到事件后,在 /dev/ 目录下创建 my_device

Q3: read() 函数如何工作?

  1. 用户进程 调用 read(fd, buffer, len)
  2. VFS 层 解析 /dev/my_device
  3. 内核调用 my_read()
  4. copy_to_user() 把数据复制到用户空间

6. 总结

  • 设备号(Major/Minor)用于唯一标识设备
  • 设备类(Class)组织设备,udev 监听 /sys/class
  • 设备文件 /dev/my_device 允许用户访问驱动
  • 字符设备驱动 通过 file_operations 绑定 open()read() 等方法

希望这篇文章能帮助你更好地理解 Linux 内核模块及其在文件系统中的映射!🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值