字符设备驱动是Linux内核中直接管理字节流设备(如串口、键盘)的核心模块,其核心框架围绕file_operations结构体展开。该结构体定义了用户空间系统调用(如read、write)与底层硬件操作的映射关系,是驱动开发的基石。
1. file_operations结构体解析
struct file_operations {
ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
int (*open)(struct inode *, struct file *);
int (*release)(struct inode *, struct file *);
long (*unlocked_ioctl)(struct file *, unsigned int, unsigned long);
// 其他操作:mmap, poll, fsync等
};
核心函数指针:
read/write:实现设备数据读写,需通过copy_to_user()/copy_from_user()完成用户-内核空间数据交换。
open/release:设备打开/关闭时初始化或释放资源(如分配缓冲区、配置硬件寄存器)。
unlocked_ioctl:处理自定义控制命令(如设置波特率),替代旧版ioctl以规避大内核锁问题。
2. 驱动注册与设备管理
设备号分配:
dev_t dev_id = MKDEV(MAJOR_NUM, MINOR_NUM); // 主设备号+次设备号
alloc_chrdev_region(&dev_id, 0, 1, "my_char_dev"); // 动态分配设备号
注册字符设备:
struct cdev my_cdev;
cdev_init(&my_cdev, &my_fops); // 绑定file_operations
cdev_add(&my_cdev, dev_id, 1); // 添加到系统
创建设备节点:
mknod /dev/mydevice c 250 0 # 手动创建设备文件
或通过class_create()和device_create()自动生成/dev节点。
3. 关键实现细节
并发控制:使用spin_lock或mutex保护共享资源(如全局缓冲区),避免竞态条件。
阻塞与非阻塞I/O:通过poll函数实现事件等待队列,支持select/epoll异步通知。
内存映射:mmap函数将设备内存映射到用户空间,加速大数据传输(如帧缓冲区)。
4. 调试与错误处理
利用printk输出调试信息(KERN_DEBUG级别)。
错误码规范:返回-EINVAL(参数无效)、-ENOMEM(内存不足)等标准错误。
资源释放:在release函数中反向释放open中分配的资源,防止内存泄漏。
总结
字符设备驱动的核心是通过file_operations桥接用户与硬件:
接口层:实现read/write等函数响应系统调用,需严格处理用户-内核数据边界;
注册层:动态分配设备号并绑定操作集,通过cdev和sysfs完成设备生命周期管理;
优化层:引入锁机制保障并发安全,支持mmap提升性能,适配阻塞/非阻塞模式。
开发时需遵循**“初始化-操作-释放”** 的闭环逻辑,结合内核调试工具定位问题。未来趋势正朝统一设备模型(Unified Device Model) 演进,强化驱动与设备树的协同能力。