在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