Linux驱动注册是内核与硬件设备交互的核心环节,其本质是将驱动程序与设备模型关联,使内核能够识别、管理和控制硬件设备,整个过程涉及模块加载、设备号分配、字符设备/平台设备注册、设备文件创建等多个步骤,需遵循Linux设备模型的规范,确保驱动与设备的正确匹配和资源的合理管理。
驱动模块初始化与卸载
Linux驱动通常以内核模块形式动态加载,通过module_init
和module_exit
宏定义初始化与清理函数,初始化函数是驱动注册的入口,需完成设备号申请、设备结构体初始化、资源分配等核心任务;清理函数则负责释放资源、注销设备,确保系统卸载模块时无内存泄漏或资源残留。
static int __init my_driver_init(void) { // 驱动注册逻辑 return 0; } static void __exit my_driver_exit(void) { // 驱动注销逻辑 } module_init(my_driver_init); module_exit(my_driver_exit);
设备号申请与管理
设备号是内核识别设备的标识,分为主设备号(标识设备类型)和次设备号(标识同一类型下的具体设备),注册驱动前需通过alloc_chrdev_region
动态申请设备号(推荐,避免冲突),或register_chrdev_region
静态指定(需确保设备号未被占用),申请成功后,需保存设备号信息,后续创建设备文件时使用;注销时通过unregister_chrdev_region
释放。
dev_t devno; alloc_chrdev_region(&devno, 0, 1, "my_device"); // 动态申请1个设备号
字符设备注册流程
字符设备是Linux中最基础的设备类型,注册需完成以下步骤:
- 初始化cdev结构体:
cdev
是字符设备的核心结构体,通过cdev_init
将其与file_operations
关联(定义驱动对用户空间提供的操作接口,如open、read、write等)。struct cdev my_cdev; cdev_init(&my_cdev, &my_fops); // my_fops为file_operations结构体 my_cdev.owner = THIS_MODULE;
- 添加cdev到内核:调用
cdev_add
将cdev注册到系统,参数包括设备号和设备数量(如需支持多个次设备号,可传入大于1的值)。cdev_add(&my_cdev, devno, 1);
- 创建设备文件:通过
class_create
创建设备类(在/sys/class下生成目录),再通过device_create
在类下创建设备文件(如/dev/my_device),使用户空间可通过文件接口访问设备。struct class *my_class = class_create(THIS_MODULE, "my_class"); device_create(my_class, NULL, devno, NULL, "my_device");
平台设备注册(适用于嵌入式系统)
对于基于总线的设备(如I2C、SPI、Platform总线),驱动注册需与设备模型匹配,以Platform总线为例:
- 定义platform_driver:包含
probe
(设备匹配成功时调用,完成硬件初始化)和remove
(设备移除时调用,清理资源)函数,以及of_match_table
(用于设备树匹配)。static const struct of_device_id my_of_match[] = { {.compatible = "vendor,my_device"}, {} }; static struct platform_driver my_platform_driver = { .probe = my_probe, .remove = my_remove, .driver = { .name = "my_device", .of_match_table = my_of_match, } };
- 注册platform_driver:通过
platform_driver_register
将驱动注册到内核,内核会遍历设备树,根据compatible属性匹配已注册的platform_device,匹配成功则调用probe函数。platform_driver_register(&my_platform_driver);
资源管理
驱动注册过程中需申请硬件资源(如内存、中断、DMA等),并在卸载时释放:
- 内存资源:通过
request_mem_region
申请物理内存,ioremap映射到虚拟地址,释放时调用release_mem_region
和iounmap
。 - 中断资源:通过
request_irq
申请中断,指定中断处理函数,释放时调用free_irq
。 - DMA资源:通过
dma_alloc_coherent
申请DMA缓冲区,释放时调用dma_free_coherent
。
关键函数总结
函数名 | 功能描述 | 主要参数 | 返回值 |
---|---|---|---|
alloc_chrdev_region | 动态申请字符设备号 | dev_t dev, unsigned baseminor, unsigned count, const char name | int(成功0,失败负值) |
cdev_init | 初始化cdev结构体,关联file_operations | struct cdev cdev, const struct file_operations fops | 无 |
cdev_add | 将cdev添加到内核设备表 | struct cdev *cdev, dev_t dev, unsigned count | int(成功0,失败负值) |
class_create | 创建设备类 | const char *name | struct class * |
device_create | 在设备类下创建设备文件 | struct class class, struct device parent, dev_t devt, void drvdata, const char fmt | struct device * |
platform_driver_register | 注册平台驱动 | struct platform_driver *drv | int(成功0,失败负值) |
Linux驱动注册的核心是构建驱动与设备的关联:字符设备通过cdev和设备文件实现,平台设备通过总线匹配和probe回调实现,整个过程需严格遵循资源申请-初始化-注册-释放的流程,确保驱动与内核设备模型的兼容性和稳定性。
相关问答FAQs
Q1:驱动注册时cdev_add失败,可能的原因及解决方法?
A:cdev_add失败通常由设备号冲突、file_operations未正确初始化或内存不足导致,解决方法:
- 检查设备号是否已被其他驱动占用(可通过
cat /proc/devices
查看); - 确认file_operations结构体中的成员函数已正确赋值(如.owner=THIS_MODULE);
- 调用cdev_del(&my_cdev)清理已分配的cdev资源,避免内存泄漏。
Q2:字符设备与块设备注册的主要区别是什么?
A:字符设备(如键盘、串口)以字节流方式访问,支持随机读写,注册时通过cdev和file_operations实现;块设备(如硬盘、U盘)以固定大小块(如512B)访问,需支持缓冲和随机存取,注册时通过gendisk结构体和bio操作接口实现,块设备需请求队列(request_queue)管理IO请求,而字符设备无需。
原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/22064.html