2. 字符设备驱动基础
任务目标
编写一个最小字符设备驱动,实现:动态分配主设备号、向内核注册设备、卸载时自动注销,并验证设备节点可正常创建与访问。
环境依赖
- Linux内核源码树(当前案例基于5.15版本验证)
- 内核头文件与编译工具链(gcc、make)
- 开发机需具备root权限(用于加载模块与创建设备节点)
核心实现
驱动代码包含完整的注册/注销逻辑,不依赖任何外部未定义函数。设备号动态分配,通过class_create自动创建设备节点,卸载时全部清理。
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "demo_char"
#define CLASS_NAME "demo_class"
static dev_t dev_num;
static struct cdev demo_cdev;
static struct class *demo_class;
static struct device *demo_device;
static int demo_open(struct inode *inode, struct file *filep)
{
return 0;
}
static ssize_t demo_read(struct file *filep, char __user *buf,
size_t len, loff_t *offset)
{
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = demo_open,
.read = demo_read,
};
static int __init demo_init(void)
{
int ret;
/* 1. 动态分配主设备号 */
ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
if (ret < 0) {
pr_err("Failed to allocate device number\n");
return ret;
}
pr_info("Allocated major=%d minor=%d\n",
MAJOR(dev_num), MINOR(dev_num));
/* 2. 初始化并添加cdev */
cdev_init(&demo_cdev, &fops);
demo_cdev.owner = THIS_MODULE;
ret = cdev_add(&demo_cdev, dev_num, 1);
if (ret < 0) {
pr_err("Failed to add cdev\n");
goto err_unregister;
}
/* 3. 创建device class,用于自动生成设备节点 */
demo_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(demo_class)) {
pr_err("Failed to create class\n");
ret = PTR_ERR(demo_class);
goto err_cdev_del;
}
/* 4. 创建设备节点 /dev/demo_char */
demo_device = device_create(demo_class, NULL, dev_num,
NULL, DEVICE_NAME);
if (IS_ERR(demo_device)) {
pr_err("Failed to create device\n");
ret = PTR_ERR(demo_device);
goto err_class_destroy;
}
pr_info("Demo char driver initialized\n");
return 0;
err_class_destroy:
class_destroy(demo_class);
err_cdev_del:
cdev_del(&demo_cdev);
err_unregister:
unregister_chrdev_region(dev_num, 1);
return ret;
}
static void __exit demo_exit(void)
{
device_destroy(demo_class, dev_num);
class_destroy(demo_class);
cdev_del(&demo_cdev);
unregister_chrdev_region(dev_num, 1);
pr_info("Demo char driver removed\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kernel Demo");
MODULE_DESCRIPTION("Minimal char device driver with dynamic major");
Makefile
obj-m := demo_char.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
编译与加载
- 将上述驱动代码保存为
demo_char.c,Makefile保存为同名文件(无后缀) - 执行
make,生成demo_char.ko - 加载模块:
sudo insmod demo_char.ko - 查看设备号分配:
dmesg | tail,应看到类似Allocated major=240 minor=0的输出 - 确认设备节点:
ls -l /dev/demo_char,应显示字符设备文件
验证方法
- 注册验证:
cat /proc/devices | grep demo_char,能看到设备名与动态分配的主设备号 - 访问验证:
sudo cat /dev/demo_char,命令不报错即说明设备节点可正常打开(read返回0,无数据输出属正常) - 注销验证:
sudo rmmod demo_char,然后确认/dev/demo_char已消失,dmesg | tail显示Demo char driver removed
常见问题
- insmod报错“Operation not permitted”:需root权限,或检查内核是否禁用模块加载
- 设备节点未自动生成:确认内核配置了
CONFIG_DEVTMPFS且/dev挂载了devtmpfs;否则需手动mknod /dev/demo_char c 主设备号 0 - rmmod提示“Module in use”:先关闭所有打开该设备的进程(如正在运行的cat)