【Linux驱动】字符设备驱动内核源码深度解析

发布时间:2026/7/6 2:42:01
【Linux驱动】字符设备驱动内核源码深度解析 字符设备号管理本小节的主线是内核如何管理哪些设备号已经分配哪些设备号可用1.1 dev_t 设备号概述在Linux内核中每个字符设备都由一个32 位的设备号dev_t唯一标识。这个 32 位数值被划分为两部分主设备号Major Number占用高 12 位bit 12-31用于标识设备驱动类型次设备号Minor Number占用低 20 位bit 0-19用于区分同一驱动下的不同设备实例内核提供了三个关键宏来操作设备号#define MINORBITS 20 #define MINORMASK ((1U MINORBITS) - 1) #define MAJOR(dev) ((unsigned int) ((dev) MINORBITS)) /* 提取主设备号 */ #define MINOR(dev) ((unsigned int) ((dev) MINORMASK)) /* 提取次设备号 */ #define MKDEV(ma,mi) (((ma) MINORBITS) | (mi)) /* 组合生成设备号 */ 设计思想主设备号相当于设备类型标识例如所有I2C设备共享一个主设备号次设备号则用于区分具体是哪个I2C设备如 MPU6050 传感器还是 EEPROM 存储器。这种分层设计使得内核可以在保持设备号空间紧凑的同时支持大量设备实例1.2 char_device_struct 结构体内核通过char_device_struct结构体来记录系统中已分配的设备号范围主设备号 次设备号区间形成一个资源管理表防止设备号冲突。内核维护了chrdevs哈希表来记录设备号的使用情况/* 定义在 fs/char_dev.c */ #define CHRDEV_MAJOR_HASH_SIZE 255 static struct char_device_struct { struct char_device_struct *next;/* 将相同哈希值的节点链接成链表 */ unsigned int major; /* 主设备号 */ unsigned int baseminor; /* 次设备号起始值 */ int minorct; /* 次设备号数量 */ char name[64]; /* 设备名称 */ struct cdev *cdev; /* 内核字符对象已废弃 */ } *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; 关键设计内核使用chrdevs维护设备号的分配信息chrdevs数组大小仅为 255但主设备号范围是 0~511甚至理论上可以更大。内核通过取模操作major % 255将主设备号映射到哈希桶。具体来说主设备号 0、255、510... 都落在桶 0主设备号 1、256、511... 都落在桶 1桶内链表按主设备号从小到大排序这种设计下在不增加数组规模的前提下理论上支持无限的主设备号范围且查找效率不受影响。1.3 __register_chrdev_region 函数__register_chrdev_region是设备号分配的核心函数负责在chrdevs哈希表中查找或插入一个空闲的设备号区间并返回对应的char_device_struct指针/* 定义在 fs/char_dev.c */ static struct char_device_struct *__register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name) { struct char_device_struct *cd, **cp; intret0; inti; /* 1. 分配新的 char_device_struct 节点 */ cd kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); if (cd NULL) return ERR_PTR(-ENOMEM); mutex_lock(chrdevs_lock); /* 2. 如果 major 0动态分配主设备号 */ if (major 0) { ret find_dynamic_major(); if (ret 0) { pr_err(CHRDEV \%s\ dynamic allocation region is full\n, name); goto out; } major ret; } /* 3. 主设备号范围校验 */ if (major CHRDEV_MAJOR_MAX) { pr_err(CHRDEV \%s\ major requested (%u) greater than max (%u)\n, name, major, CHRDEV_MAJOR_MAX - 1); ret -EINVAL; goto out; } cd-major major; cd-baseminor baseminor; cd-minorct minorct; strlcpy(cd-name, name, sizeof(cd-name)); /* 4. 计算哈希桶位置 */ i major_to_index(major); /* 5. 在链表中找到合适的插入位置按主设备号、次设备号排序 */ for (cp chrdevs[i]; *cp; cp (*cp)-next) { if ((*cp)-major major || ((*cp)-major major ((*cp)-baseminor baseminor || (*cp)-baseminor (*cp)-minorct baseminor))) break; } /* 6. 检查次设备号是否冲突三种重叠检测 */ if (*cp (*cp)-major major) { int old_min (*cp)-baseminor; intold_max (*cp)-baseminor (*cp)-minorct-1; intnew_minbaseminor; intnew_maxbaseminorminorct-1; /* 新范围与已有范围重叠 → 冲突 */ if (new_max old_min new_max old_max) { ret -EBUSY; goto out; } if (new_min old_max new_min old_min) { ret -EBUSY; goto out; } /* 新范围完全覆盖已有范围 */ if (new_min old_min new_max old_max) { ret -EBUSY;