Linux字符设备驱动开发的详细步骤
1. 确定主设备号
- 手动指定:明确设备号时,使用
register_chrdev_region()
静态申请(需确保未被占用)。 - 动态分配:通过
alloc_chrdev_region()
由内核自动分配主设备号(更灵活,推荐)。dev_t dev; alloc_chrdev_region(&dev, 0, 1, "mydevice"); // 动态分配主设备号
2. 定义file_operations结构体
- 结构体作用:关联用户空间系统调用与驱动函数(如
open/read/write
)。 - 关键成员:
static struct file_operations fops = {.owner = THIS_MODULE, // 模块所有者标识.open = drv_open, // 设备打开函数.read = drv_read, // 数据读取函数.write = drv_write, // 数据写入函数.release = drv_release, // 设备关闭函数 };
3. 实现驱动函数
需实现drv_open
、drv_read
、drv_write
等函数,并处理硬件交互逻辑:
- 示例:
open
函数(初始化硬件或分配资源):static int drv_open(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device opened\n");return 0; }
- 数据传输函数:需通过
copy_from_user()
和copy_to_user()
实现用户空间与内核空间的数据交换。
4. 注册驱动到内核
- 入口函数:通过
module_init()
指定驱动加载时的初始化函数:static int __init mydrv_init(void) {// 注册字符设备,主设备号设为0表示自动分配int ret = register_chrdev(0, "mydevice", &fops);if (ret < 0) {printk(KERN_ERR "Register failed\n");return ret;}// 自动创建设备节点(后续补充)return 0; } module_init(mydrv_init);
- 出口函数:通过
module_exit()
指定驱动卸载时的清理函数:static void __exit mydrv_exit(void) {unregister_chrdev(major, "mydevice"); } module_exit(mydrv_exit);
5. 自动创建设备节点
- 使用
class_create
和device_create
:static struct class *dev_class; static dev_t dev;static int __init mydrv_init(void) {// 创建设备类dev_class = class_create(THIS_MODULE, "mydevice_class");// 创建设备节点(/dev/mydevice)device_create(dev_class, NULL, dev, NULL, "mydevice");return 0; }static void __exit mydrv_exit(void) {device_destroy(dev_class, dev); // 销毁节点class_destroy(dev_class); // 销毁类 }
- 作用:用户空间可通过
/dev/mydevice
直接访问设备,无需手动mknod
。
- 作用:用户空间可通过
6. 其他完善步骤
- 错误处理:检查
register_chrdev
、class_create
等函数的返回值,避免资源泄漏。 - 资源释放:在出口函数中释放所有申请的资源(如设备号、内存)。
- 兼容性:确保驱动代码与内核版本匹配,遵循内核编码规范。
总结流程图
驱动初始化(入口函数)
├─ 分配设备号(动态/静态)
├─ 初始化file_operations结构体
├─ 注册字符设备(register_chrdev)
├─ 创建设备类(class_create)
└─ 创建设备节点(device_create)驱动卸载(出口函数)
├─ 注销字符设备(unregister_chrdev)
├─ 销毁设备节点(device_destroy)
└─ 销毁设备类(class_destroy)
通过以上步骤,可完成一个完整的Linux字符设备驱动开发流程。具体实现需结合硬件特性调整函数逻辑(如中断处理、DMA操作)