2. 字符设备驱动基础:设备号概念与分配
在 Linux 字符设备驱动中,设备号是内核与用户空间识别设备的核心标识。每个字符设备在系统中都拥有唯一的设备号,内核通过它来查找对应的驱动程序并完成文件操作。本章将系统讲解设备号的构成、分配与释放机制,为后续实现中断处理和 ioctl 接口打下基础。
2.1 设备号概念
设备号是一个 dev_t 类型的 32 位无符号整数,由两部分组成:
- 主设备号(Major Number):高 12 位,用于标识设备对应的驱动程序。内核通过主设备号找到驱动注册的
file_operations结构体。 - 次设备号(Minor Number):低 20 位,由驱动程序内部使用,通常用于区分同一驱动管理的多个物理或逻辑设备实例。
关键理解: 主设备号是“驱动 ID”,次设备号是“设备实例 ID”。同一个主设备号下的不同次设备号共享同一套驱动操作函数。
2.2 主设备号与次设备号的职责划分
| 属性 | 主设备号 | 次设备号 |
|---|---|---|
| 位数 | 12 位(0–4095) | 20 位(0–1048575) |
| 内核用途 | 查找驱动注册的 file_operations |
驱动内部区分设备实例 |
| 分配方式 | 静态指定 或 动态分配 | 由驱动开发者自行规划 |
| 典型示例 | 89(I2C 设备) | 0–3(同一 I2C 总线上的 4 个从设备) |
| 用户空间可见 | ls -l /dev 第一列数字 |
ls -l /dev 第二列数字 |
实践提示: 在
/dev 目录下执行 ls -l,你会看到类似 crw-rw---- 1 root root 10, 235 的输出,其中 10 是主设备号,235 是次设备号。
2.3 设备号分配与释放 API
Linux 内核提供两组核心函数用于设备号的注册与注销。驱动开发者必须在模块加载时分配设备号,在卸载时释放。
2.3.1 静态分配
当驱动使用已知的、未占用的主设备号时,可以静态指定:
#include <linux/fs.h>
int register_chrdev_region(dev_t first, unsigned int count, const char *name);
// first: 起始设备号(主设备号 << 20 | 起始次设备号)
// count: 连续分配的次设备号数量
// name: 设备名称(显示在 /proc/devices)
// 返回值:0 成功,负值失败
2.3.2 动态分配
推荐使用动态分配,避免主设备号冲突:
int alloc_chrdev_region(dev_t *dev, unsigned int baseminor, unsigned int count, const char *name);
// dev: 输出参数,返回分配到的起始设备号
// baseminor: 起始次设备号
// count: 连续分配的次设备号数量
// name: 设备名称
// 返回值:0 成功,负值失败
2.3.3 释放设备号
无论使用哪种分配方式,释放接口相同:
void unregister_chrdev_region(dev_t first, unsigned int count);
// first: 之前分配的起始设备号
// count: 与分配时相同的数量
常见错误: 释放时
count 参数必须与分配时一致,否则会导致设备号泄漏或错误释放其他驱动的设备号。建议在模块卸载函数中立即调用释放。
2.4 查看已分配设备号
系统运行时,可以通过以下方式查看当前已注册的设备号:
/proc/devices:显示所有已注册的主设备号及其对应的驱动名称。/sys/dev/char/:按主设备号/次设备号组织的 sysfs 目录。ls -l /dev:显示设备文件对应的设备号。
调试技巧: 在驱动开发中,如果
insmod 失败并提示 Device or resource busy,通常是因为主设备号已被占用。此时应改用 alloc_chrdev_region 动态分配。
2.5 完整示例:设备号分配与释放
以下代码展示了一个字符设备驱动模块中设备号管理的标准模式:
#include <linux/module.h>
#include <linux/fs.h>
#define DEVICE_NAME "my_char_dev"
#define MINOR_COUNT 4
static dev_t dev_num;
static int __init my_init(void)
{
int ret;
/* 动态分配 4 个连续设备号,起始次设备号为 0 */
ret = alloc_chrdev_region(&dev_num, 0, MINOR_COUNT, DEVICE_NAME);
if (ret < 0) {
pr_err("Failed to allocate device numbers\n");
return ret;
}
pr_info("Allocated major=%d, minor=%d\n",
MAJOR(dev_num), MINOR(dev_num));
return 0;
}
static void __exit my_exit(void)
{
unregister_chrdev_region(dev_num, MINOR_COUNT);
pr_info("Device numbers released\n");
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Driver Expert");
MODULE_DESCRIPTION("Device number allocation example");
2.6 本章小结
| 知识点 | 核心要点 |
|---|---|
| 设备号结构 | dev_t 高 12 位主设备号 + 低 20 位次设备号 |
| 主设备号作用 | 内核查找驱动 file_operations 的索引 |
| 次设备号作用 | 驱动内部区分多个设备实例 |
| 静态分配 | register_chrdev_region(),需已知空闲主设备号 |
| 动态分配 | alloc_chrdev_region(),推荐使用,避免冲突 |
| 释放接口 | unregister_chrdev_region(),参数必须与分配一致 |