在Linux系统中,设备号是内核识别和管理设备的标识符,分为主设备号(major number)和次设备号(minor number),主设备号用于标识设备类型(如字符设备或块设备),次设备号用于区分同一类型下的不同设备实例,注册设备号是设备驱动开发的核心步骤之一,目的是将设备号与驱动程序关联,使内核能够正确地将设备请求路由到对应的驱动处理函数,本文将详细介绍Linux中注册设备号的方法、相关函数及注意事项。
设备号的基本概念
主设备号通常由8位或12位(取决于内核配置)组成,范围因架构而异(如x86_32下为0-255,x86_64下为0-4095),次设备号通常为8位或20位,范围0-255或0-1048575,设备号通过dev_t
类型表示,在内核中定义为32位或64位无符号整数,其中高位为主设备号,低位为次设备号,可通过宏MAJOR()
和MINOR()
提取主次设备号,MKDEV()
将主次设备号合并为dev_t
类型。
设备号的分配方式
设备号分配分为静态分配和动态分配两种,具体选择需根据驱动开发场景决定。
静态分配
静态分配是指开发者手动指定主设备号和次设备号范围,适用于设备号已知且固定的情况(如标准设备或厂商预留设备号),需确保指定设备号未被其他驱动使用,可通过/proc/devices
文件查看已分配的主设备号。
核心函数:register_chrdev_region()
int register_chrdev_region(dev_t from, unsigned count, const char *name);
from
:起始设备号(由MKDEV(major, minor)
生成);count
:连续分配的设备号数量;name
:设备名称(会出现在/proc/devices
中)。
返回值:成功返回0,失败返回负错误码(如-EBUSY表示设备号冲突)。
示例:
分配主设备号250,次设备号0-3,共4个设备号:
dev_t dev_no; int major = 250, minor = 0, count = 4; dev_no = MKDEV(major, minor); if (register_chrdev_region(dev_no, count, "my_dev") < 0) { printk(KERN_ERR "Failed to register device regionn"); return -EBUSY; }
注意事项:静态分配需提前确认设备号可用性,否则可能导致驱动加载失败。
动态分配
动态分配是指由内核自动分配可用设备号,适用于设备号不确定或开发阶段,避免手动分配冲突,内核会从指定范围或全局范围内寻找未使用的设备号返回给驱动。
核心函数:alloc_chrdev_region()
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
dev
:输出参数,用于返回分配到的起始设备号;baseminor
:起始次设备号(通常设为0);count
:连续分配的设备号数量;name
:设备名称。
返回值:成功返回0,失败返回负错误码。
示例:
动态分配设备号,次设备号从0开始,共4个:
dev_t dev_no; int baseminor = 0, count = 4; if (alloc_chrdev_region(&dev_no, baseminor, count, "my_dev") < 0) { printk(KERN_ERR "Failed to allocate device regionn"); return -ENOMEM; } major = MAJOR(dev_no); // 获取分配的主设备号 minor = MINOR(dev_no); // 获取分配的次设备号
注意事项:动态分配的设备号可能每次不同,驱动需通过MAJOR()
和MINOR()
提取并保存,后续注销时需使用相同的dev_t
。
设备号的释放
无论采用静态还是动态分配,卸载驱动时必须释放设备号,否则会导致设备号泄漏,影响后续驱动加载。
核心函数:unregister_chrdev_region()
void unregister_chrdev_region(dev_t from, unsigned count);
from
:需要释放的起始设备号;count
:释放的设备号数量,需与注册时一致。
示例:
unregister_chrdev_region(dev_no, count); // dev_no为注册时的起始设备号
静态分配与动态分配对比
对比项 | 静态分配 | 动态分配 |
---|---|---|
核心函数 | register_chrdev_region() |
alloc_chrdev_region() |
设备号来源 | 开发者手动指定 | 内核自动分配 |
优点 | 设备号固定,便于用户空间访问 | 避免冲突,无需手动检查设备号可用性 |
缺点 | 需提前确认设备号可用,易冲突 | 设备号不固定,用户空间需动态获取 |
适用场景 | 标准设备、厂商预留设备号 | 开发阶段、设备号不确定的驱动 |
设备号注册与字符设备驱动的关系
注册设备号是字符设备驱动开发的子步骤,需与cdev
(字符设备核心结构体)结合使用。cdev
用于关联设备号和驱动操作函数集(file_operations
),完整的字符设备驱动流程包括:
- 分配
cdev
结构体(cdev_alloc()
或cdev_init()
); - 初始化
cdev
并绑定file_operations
; - 注册设备号(静态/动态);
- 添加
cdev
到内核(cdev_add()
); - 创建设备节点(通过udev/mdev自动或手动创建)。
示例代码片段(简化版):
#include <linux/cdev.h> #include <linux/fs.h> struct cdev my_cdev; dev_t dev_no; int major = 250, minor = 0, count = 4; static int my_open(struct inode *inode, struct file *filp) { return 0; } static struct file_operations fops = { .owner = THIS_MODULE, .open = my_open, }; static int __init my_init(void) { // 1. 注册设备号(静态) dev_no = MKDEV(major, minor); if (register_chrdev_region(dev_no, count, "my_dev") < 0) { return -EBUSY; } // 2. 初始化并添加cdev cdev_init(&my_cdev, &fops); my_cdev.owner = THIS_MODULE; if (cdev_add(&my_cdev, dev_no, count) < 0) { unregister_chrdev_region(dev_no, count); return -EFAULT; } return 0; } static void __exit my_exit(void) { cdev_del(&my_cdev); // 删除cdev unregister_chrdev_region(dev_no, count); // 释放设备号 } module_init(my_init); module_exit(my_exit);
相关问答FAQs
Q1:静态分配设备号时,如何确认设备号是否已被占用?
A1:可通过以下方式确认:
- 查看
/proc/devices
文件,记录已分配的主设备号及对应设备名,避免重复; - 使用
cat /proc/devices | grep 设备名
检查特定设备名是否已占用主设备号; - 在驱动代码中,若
register_chrdev_region()
返回-EBUSY,表示设备号冲突,需更换其他设备号。
Q2:动态分配的设备号如何在用户空间获取?
A2:用户空间可通过以下方式获取动态分配的设备号:
- 在驱动模块初始化时,通过
printk
打印分配的主设备号(如printk(KERN_INFO "Major: %dn", MAJOR(dev_no))
); - 加载驱动后,查看
/proc/devices
文件,根据设备名找到对应主设备号; - 结合
udev
规则:在/etc/udev/rules.d/
下创建规则文件(如99-mydev.rules
),通过KERNEL=="mydev*", ATTR{dev}=="%M:%m", SYMLINK+="mydev"
,其中%M
和%m
分别为主次设备号占位符,udev
会自动创建设备节点并包含设备号信息。
原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/36171.html