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):区分同类设备的不同实例
设备号注册方式
- 动态分配
dev_t dev_num;
alloc_chrdev_region(&dev_num, 0, 1, "my_device");
- 静态指定
#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
设备文件。
步骤:
- 创建设备类
struct class *my_class;
my_class = class_create(THIS_MODULE, "my_class");
- 创建设备文件
struct device *my_device;
my_device = device_create(my_class, NULL, dev_num, NULL, "my_device");
加载后,ls /dev/my_device
应可看到设备文件。
- 卸载时删除
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()
函数如何工作?
- 用户进程 调用
read(fd, buffer, len)
- VFS 层 解析
/dev/my_device
- 内核调用
my_read()
copy_to_user()
把数据复制到用户空间
6. 总结
- 设备号(Major/Minor)用于唯一标识设备
- 设备类(Class)组织设备,
udev
监听/sys/class
- 设备文件
/dev/my_device
允许用户访问驱动 - 字符设备驱动 通过
file_operations
绑定open()
、read()
等方法
希望这篇文章能帮助你更好地理解 Linux 内核模块及其在文件系统中的映射!🚀