字符设备驱动程序由一个cdev结构描述:
struct cdev {
struct kobject kobj; // 每个 cdev 都是一个 kobject
struct module *owner; // 指向实现驱动的模块
const struct file_operations *ops; // 操纵这个字符设备文件的方法
struct list_head list; // 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
dev_t dev; //设备号,int 类型,高12位为主设备号,低20位为次设备号
unsigned int count; // 设备范围号大小
};
list字段是双向循环链表的首部,用于收集相同字符设备驱动程序所对应的字符设备文件的索引节点。可能很多设备文件具有相同的设备号,并对应于相同的字符设备。此外,一个设备驱动程序对应的设备号可以是一个范围,而不仅仅是一个号;设备号位于同一范围内的所有设备文件由同一个字符设备驱动程序处理
struct cdev *cdev_alloc(void)
{
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
if (p) {
INIT_LIST_HEAD(&p->list);
kobject_init(&p->kobj, &ktype_cdev_dynamic);
}
return p;
}
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev); 注1;
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
由代码可以看到,两个函数功能基本上是一致的,都是初始化内部的kobject,只是cdev_init还给ops进行了初始化。
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}
register_chrdev_region接收三个参数:初始设备号(主+次)请求设备号范围大小,以及这个范围内的设备号对应的设备驱动的名称。当次设备号数目过多(count过多)的时候,次设备号可能会溢出到下一个主设备。因此我们在for语句中可以看到,首先得到下一个主设备号(其实也是一个设备号,只不过此时的次设备号为0)并存储于next中。然后判断在from的基础上再追加count个设备是否已经溢出到下一个主设备号。如果没有溢出(next小于to),那么整个for语句就只执行个一次__register_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, **cp;
int ret = 0;
int i;
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);//配内存并用零来填充(这就是用kzalloc而不是kmalloc的原因)
if (cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
/* temporary */
if (major == 0) {//major为0,也就是未指定一个具体的主设备号,需要动态分配
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {//找合适的位置
if (chrdevs[i] == NULL)
break;
}
if (i == 0) {
ret = -EBUSY;
goto out;
}
major = i;//若找到后,那么i不仅代表这组设备的主设备号,也代表其在散列表中的关键字
ret = major;
}
cd->major = major;//将参数中的值依次赋给cd变量的对应字段
cd->baseminor = baseminor;
cd->minorct = minorct;
strlcpy(cd->name, name, sizeof(cd->name));
i = major_to_index(major);//除模255运算
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)//寻找正确的位置
if ((*cp)->major > major || //主设备号(*(cp)->major)大于我们所分配的主设备号(major)(有序)
((*cp)->major == major &&//如果(*cp)结点和cd结点的主设备号相同
(((*cp)->baseminor >= baseminor) || //前者的次设备号起点比cd结点的大
((*cp)->baseminor + (*cp)->minorct > baseminor))))//cd结点的次设备号起点小于(*cp)结点的次设备号的终点
break;
/* Check for overlapping minor ranges. */
if (*cp && (*cp)->major == major) {//主设备号相同时才需要冲突判断
int old_min = (*cp)->baseminor;//计算出新老次设备号的范围,
int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
int new_min = baseminor;
int new_max = baseminor + minorct - 1;
/* New driver overlaps from the left. */
if (new_max >= old_min && new_max <= old_max) {//新范围终点是否在老范围的之间
ret = -EBUSY;
goto out;
}
/* New driver overlaps from the right. */
if (new_min <= old_max && new_min >= old_min) {//新范围的起点是否在老范围之间
ret = -EBUSY;
goto out;
}
}
//到这里的话说明没有冲突
cd->next = *cp;//将char_device_struct描述符插入到中途链表中
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
这里有一个结构char_device_struct,用于注册字符设备驱动,保存已经注册字符驱动的一些信息,如主次设备号,次设备号的数量,驱动的名字等,便于字符设备驱动注册时索引查找
static struct char_device_struct {
/*被255整除后相同的设备号链成一个单向链表*/
struct char_device_struct *next;
unsigned int major; /* 主设备号 */
unsigned int baseminor; /* 次设备起始号 */
int minorct; /* 次设备号范围 */
char name[64]; /* 驱动的名字 */
struct file_operations *fops; /* 保存文件操作指针,目前没有使用 */
struct cdev *cdev; /* will die */ /*目前没有使用*/
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; /* CHRDEV_MAJOR_HASH_SIZE = 255 */
另外一个函数就是alloc_chrdev_region,它是动态的分配一个主设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
该函数接参数为设备号范围内的初始设备号,范围大小,以及设备驱动的名称,然后调用 __register_chrdev_region。
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
它初始化dev和count字段,然后调用kobj_map依次建立设备驱动程序模型的数据结构,把设备号范围复制到设备驱动程序的描述符中。
void __init chrdev_init(void)
{
cdev_map = kobj_map_init(base_probe, &chrdevs_lock);
bdi_init(&directly_mappable_cdev_bdi);
}
看一下kobj_map结构
struct kobj_map {
struct probe {
struct probe *next; /* 散列冲突表中的下一个元素 */
dev_t dev; /* 字符设备驱动的设备号,包含主设备号(高12位)和次设备号(低20位) */
unsigned long range; /* 次设备号范围 */
struct module *owner; /* 表明模块的归属,是THIS_MODULE */
kobj_probe_t *get; /* 这里可以保存传进来的base_probe函数指针,探测谁这个设备号范围 */
int (*lock)(dev_t, void *); /* 增加设备号范围内拥有者的引用计数器 */
void *data;/*私有数据*/
} *probes[255]; /* 虽然大小只有255,但采用了链表的形式,可以支持到4096个主设 */
struct mutex *lock; /* 保存全局互斥锁,用于关键区域的保护 */
};
kobj_map_init()函数传进去了两个参数,base_probe函数和chrdevs_lock互斥变量,返回一个struct kobj_map类型的指针。base_probe调用了request_module()函数,用于加载与字符驱动相关的驱动程序,被加载的驱动命名方式是char-major-主设备号-次设备号。request_module()函数,你可以看看该函数上头的英文注释,它最终会调用应用层空间的modprobe命令来加载驱动程序。实际上,没有使用该项功能
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;/* 所有指针都指向同一个base */
p->lock = lock;
return p;
}
回到cdev_add,接着调用kobj_map,其作用就是分配一个struct probe结构体,填充该结构体中的变量并将其加入到全局的cdev_map中,等下次来找的时候能找到(使用kobj_lookup()函数
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 n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;//根据设备号和范围,计数出需要多少个probe结构(range没有超过255的话一个就够了)
unsigned index = MAJOR(dev);
unsigned i;
struct probe *p;
if (n > 255)
n = 255;
p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);//分配probe结构
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; //把cdev结构保存在了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)//保证probe中ranger大的靠后
s = &(*s)->next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock);
return 0;
}
到这里,该字符设备就注册进去了
字符设备驱动注册另外一个经常使用的函数 就是register_chrdev,它的参数为一个主设备号,驱动程序名字以及一个指针fops,它会分配该主设备相关的0~255的次设备号范围并进行注册,建立一个缺省的 cdev 结构. 使用这个接口的驱动必须准备好处理对所有 256 个次编号的 open 调用( 不管它们是否对应真实设备 ), 它们不能使用大于 255 的主或次编号.
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;
}
这个函数和前面讲的驱动注册基本是一致的,只是把一些流程给综合起来了。
再总结一下三个主要的结构: