字符设备驱动程序源码
内核字符设备用struct cdev表示,通常情况下cdev内嵌到其他设备中:
struct cdev {
struct kobject kobj; // 内嵌的内核对象
struct module *owner; // 字符设备驱动程序所在的内核模块对象指针
const struct file_operations *ops;
struct list_head list; // 用来将系统中的字符设备形成链表
dev_t dev; // 字符设备的设备号,由主设备号和次设备号构成
// 隶属于同一主设备号的次设备号的个数
// 用于表示由当前设备驱动程序控制的实际同类设备的数量
unsigned int count;
};
可以使用cdev_init来初始化一个字符设备:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
其中的kobject_init用来初始化内含的kobject对象。
字符设备初始化完成后,就可以把它加入到系统中,这样别的模块才可以使用它。把一个字符设备加入到系统中所需调用的函数为 cdev_add,它在源码中的实现如下:
p
: 指向字符设备结构 (struct cdev
) 的指针,描述该设备。dev
: 设备号(包含主设备号和起始次设备号)。count
: 该设备支持的连续次设备号数量。
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
int error;
p->dev = dev;
p->count = count;
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_add 的核心功能通过 kobj_map 函数来实现,后者通过操作一个全局变量 cdev_map 来把设备(*p)加入到其中的哈希链表中,cdev_map的类型定义为struct kobj_map
:
kobj_map
是一个字符设备映射结构,包含以下关键字段:
probes
: 存储每个主设备号对应的probe
链表。lock
: 互斥锁,用于保护probes
的操作。
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;
};
kobj_map函数的具体实现如下:
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)
{
// 根据dev_t和 range 设备数量计算有横跨了多少个主设备号:
// 表示设备号范围(dev, dev+range)中不同的主设备号的个数
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
unsigned index = MAJOR(dev);
unsigned i;
struct probe *p;
// 判断n是否大于255,如果n>255,则将n=255
if (n > 255)
n = 255;
// 申请n个probe大小内存,一个probe相当于一个主设备号
p = kmalloc_array(n, sizeof(struct probe), GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
// 初始化申请到的probe
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); // 对probe数据加锁
// 将申请到的probe(probe个数表示有几个主设备号),
// 按照从小到大顺序插入到主设备号对应的domain->probes[index % 255]中
for (i = 0, p -= n; i < n; i++, p++, index++) {
struct probe **s = &domain->probes[index % 255];
while (*s && (*s)->range < range) // 按照range升序插入
s = &(*s)->next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock); // 释放probe 数据锁
return 0;
}
根据kobj_map函数的实现可以发现即便主设备号不同的两个设备也有可能落到同一个probe槽位;并且在添加相同的主设备号设备时,会根据其入参count(支持次设备个数)位序添加到对应链表中。