字符设备的申请和注册
设备号设计原则
一般一个主设备号的一段连续子设备号共用一个驱动,称为cdev实例,所以有主设备号和子设备号之分
struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; // 主设备号 + 第一个子设备号 unsigned int count; // 子设备号数量 } __randomize_layout;
一个cdev实例共用一个 struct file_operation
设备号的申请
god@god-virtual-machine:/proc/bus/input$ cat /proc/devices Character devices: 1 mem 4 /dev/vc/0 4 tty 4 ttyS 5 /dev/tty 5 /dev/console 5 /dev/ptmx 5 ttyprintk 6 lp 7 vcs 10 misc 13 input ...... 252 ndctl 253 tpm 254 gpiochip Block devices: 7 loop 8 sd ...... 135 sd 253 device-mapper 254 mdp 259 blkext
由上可知,需要设备号的设备分为字符设备和块设备,数字表示主设备号
以下介绍的,无论是设备号,还是设备注册,都是指字符设备。
以下函数功能,包含了设备号的申请、字符设备的申请、字符设备添加到内核的全局变量
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) { return __register_chrdev(major, 0, 256, name, fops); }
以下函数,只包含设备号的申请,可以跨主设备号。需要指定主设备号,所以属于静态申请设备号。from是起始设备号,由12位主设备号 + 20子设备号组成32bit值,count是申请设备号的数量
int register_chrdev_region(dev_t from, unsigned count, const char *name)
以下函数,动态申请设备号,申请成功,设备号记录在入参的dev
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
以下函数,只包含设备号的申请,不能跨主设备号,alloc_chrdev_region()、register_chrdev() 和 register_chrdev_region() 最终都是调用此函数进行设备号的申请
major为0表示动态申请
static struct char_device_struct *__register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name)
register_chrdev |
设备号的申请,字符设备的申请、字符设备添加到内核的全局变量 |
register_chrdev_region |
设备号的申请(函数名具有迷惑性,没有字符设备添加到内核功能) |
alloc_chrdev_region |
设备号的申请 |
__register_chrdev_region
|
设备号的申请(函数名具有迷惑性,没有字符设备添加到内核功能) |
static struct char_device_struct *__register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name) { struct char_device_struct *cd, *curr, *prev = NULL; int ret; int i; if (major >= CHRDEV_MAJOR_MAX) { pr_err("CHRDEV \"%s\" major requested (%u) is greater than the maximum (%u)\n", name, major, CHRDEV_MAJOR_MAX-1); return ERR_PTR(-EINVAL); } if (minorct > MINORMASK + 1 - baseminor) { pr_err("CHRDEV \"%s\" minor range requested (%u-%u) is out of range of maximum range (%u-%u) for a single major\n", name, baseminor, baseminor + minorct - 1, 0, MINORMASK); return ERR_PTR(-EINVAL); } cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); if (cd == NULL) return ERR_PTR(-ENOMEM); mutex_lock(&chrdevs_lock); 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; } ret = -EBUSY; i = major_to_index(major); for (curr = chrdevs[i]; curr; prev = curr, curr = curr->next) { if (curr->major < major) continue; if (curr->major > major) break; if (curr->baseminor + curr->minorct <= baseminor) continue; if (curr->baseminor >= baseminor + minorct) break; goto out; } cd->major = major; cd->baseminor = baseminor; cd->minorct = minorct; strlcpy(cd->name, name, sizeof(cd->name)); if (!prev) { cd->next = curr; chrdevs[i] = cd; } else { cd->next = prev->next; prev->next = cd; } mutex_unlock(&chrdevs_lock); return cd; out: mutex_unlock(&chrdevs_lock); kfree(cd); return ERR_PTR(ret); }
由此函数可知,主设备号虽然有12位,但是主设备号的范围只支持 0~511,子设备号则占满20bit。
传入的major主设备号是否为0,如果为0 则表明为动态申请内存,则调用find_dynamic_major()申请一个没有使用的主设备号。
由于主设备号是0~511,而chrdevs数组的容量是255,所以把每个数组成员都设计为链表,即每个数组成员可以对应多个主设备号
上图是没有考虑子设备号情况,考虑到子设备号,实际的数据结构如下,每一个框都对应一个 struct char_device_struct 实例。
static int find_dynamic_major(void) { int i; struct char_device_struct *cd; for (i = ARRAY_SIZE(chrdevs)-1; i >= CHRDEV_MAJOR_DYN_END; i--) { if (chrdevs[i] == NULL) return i; } for (i = CHRDEV_MAJOR_DYN_EXT_START; i >= CHRDEV_MAJOR_DYN_EXT_END; i--) { for (cd = chrdevs[major_to_index(i)]; cd; cd = cd->next) if (cd->major == i) // i这个主设备号已经被占了 break; if (cd == NULL) // i这个设备号没有被占 return i; } return -EBUSY; }
先看 254 ~ 234,是否有未被占用的主设备号;再看 511 ~ 384。由此可知,动态申请的设备号只能在这两个范围内,其他用于静态分配
字符设备添加到内核的全局变量cdev_map
函数调用关系:
register_chrdev()
__register_chrdev()
__register_chrdev_region() 申请设备号
cdev_alloc() 字符设备申请
cdev_add() 字符设备添加到内核的全局变量
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) { return __register_chrdev(major, 0, 256, name, fops); }
int __register_chrdev(unsigned int major, unsigned int baseminor, unsigned int count, const char *name, const struct file_operations *fops) { struct char_device_struct *cd; struct cdev *cdev; int err = -ENOMEM; cd = __register_chrdev_region(major, baseminor, count, name); if (IS_ERR(cd)) return PTR_ERR(cd); cdev = cdev_alloc(); if (!cdev) goto out2; cdev->owner = fops->owner; cdev->ops = fops; kobject_set_name(&cdev->kobj, "%s", name); err = cdev_add(cdev, MKDEV(cd->major, baseminor), count); if (err) goto out; cd->cdev = cdev; return major ? 0 : cd->major; out: kobject_put(&cdev->kobj); out2: kfree(__unregister_chrdev_region(cd->major, baseminor, count)); return err; }
int cdev_add(struct cdev *p, dev_t dev, unsigned count) { int error; p->dev = dev; p->count = count; if (WARN_ON(dev == WHITEOUT_DEV)) return -EBUSY; error = kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p); if (error) return error; kobject_get(p->kobj.parent); return 0; }
每个cdev实例对应一个主设备号的多个连续子设备,每个cdev实例都会存储在全局变量cdev_map中,类型为struct kobj_map *,cdev_map的初始化如下:
struct kobj_map { struct probe { struct probe *next; dev_t dev; unsigned long range; struct module *owner; kobj_probe_t *get; int (*lock)(dev_t, void *); void *data; } *probes[255]; struct mutex *lock; };
void __init chrdev_init(void) { cdev_map = kobj_map_init(base_probe, &chrdevs_lock); }
struct kobj_map *kobj_map_init(kobj_probe_t *base_probe, struct mutex *lock) { struct kobj_map *p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL); struct probe *base = kzalloc(sizeof(*base), GFP_KERNEL); int i; if ((p == NULL) || (base == NULL)) { kfree(p); kfree(base); return NULL; } base->dev = 1; base->range = ~0; base->get = base_probe; for (i = 0; i < 255; i++) p->probes[i] = base; p->lock = lock; return p; }
全局变量cdev_map内有一个 struct probe*类型的数组,容量为255,每个数组成员都是一个链表,使用起来和数组chrdevs一样
int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range, struct module *module, kobj_probe_t *probe, int (*lock)(dev_t, void *), void *data) { unsigned int n = MAJOR(dev + range - 1) - MAJOR(dev) + 1; unsigned int index = MAJOR(dev); unsigned int i; struct probe *p; if (n > 255) n = 255; p = kmalloc_array(n, sizeof(struct probe), GFP_KERNEL); if (p == NULL) return -ENOMEM; for (i = 0; i < n; i++, p++) { p->owner = module; p->get = probe; p->lock = lock; p->dev = dev; p->range = range; p->data = data; } mutex_lock(domain->lock); for (i = 0, p -= n; i < n; i++, p++, index++) { struct probe **s = &domain->probes[index % 255]; while (*s && (*s)->range < range) s = &(*s)->next; p->next = *s; *s = p; } mutex_unlock(domain->lock); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
2018-03-17 linux 系统编程 ------ 串口编程