cdev和kobj_map:
内核中所有的字符设备都会记录在一个 kobj_map 结构的 cdev_map
变量中。这个结构的变量中包含一个散列表用来快速存取所有的对象。kobj_map() 函数就是用来把字符设备编号和 cdev 结构变量一起保存到 cdev_map
这个散列表里。当后续要打开一个字符设备文件时,通过调用 kobj_lookup() 函数,根据设备编号就可以找到 cdev 结构变量,从而取出其中的 ops
字段。
kobj_map函数中哈希表的实现原理和前面注册分配设备号中的几乎完全一样,通过要加入系统的设备的主设备号major(major=MAJOR(dev))来获得probes数组的索引值i(i
= major % 255),然后把一个类型为struct probe的节点对象加入到probes[i]所管理的链表中,如图2-6所示。其中struct
probe所在的矩形块中的深色部分是我们重点关注的内容,记录了当前正在加入系统的字符设备对象的有关信息。其中,dev是它的设备号,range是从次设备号开始连续的设备数量,data是一void
*变量,指向当前正要加入系统的设备对象指针p。如图展示了两个满足主设备号major % 255 =
2的字符设备通过调用cdev_add之后,cdev_map所展现出来的数据结构状态。
先看kobj_map相关的代码
涉及到的文件
-
typedef struct kobject *kobj_probe_t(dev_t, int *, void *);
-
struct kobj_map;
-
int kobj_map(struct kobj_map *, dev_t, unsigned long, struct module *, kobj_probe_t *, int (*)(dev_t, void *), void *);
-
void kobj_unmap(struct kobj_map *, dev_t, unsigned long);
-
struct kobject *kobj_lookup(struct kobj_map *, dev_t, int *);
-
struct kobj_map *kobj_map_init(kobj_probe_t *, struct mutex *);
-
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; /* 指向struct cdev对象 */
-
} *probes[255];
-
struct mutex *lock;
-
}
结构体中有一个互斥锁lock,一个probes[255]数组,数组元素为struct probe的指针,根据下面的函数作用来看,kobj_map结构体是管理设备号及其对应的设备的,而kobj_map函数就是将制定的设备号加入到该数组,kobj_lookup则查找该结构体,然后返回对应设备号的kobject对象,利用
利用该kobject对象,我们可以得到包含它的对象如cdev。
struct probe结构体中的get函数指针就是用来获得kobject对象的,可能不同类型的设备获取的方式不同,我现在就看过cdev的exact_match函数。
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)
-
{
-
unsigned n = MAJOR(dev+range-1) - MAJOR(dev) + 1;
-
unsigned index = MAJOR(dev);
-
unsigned i;
-
struct probe *p;
-
-
if (n > 255) /* 若n > 255,则超出了kobj_map中probes数组的大小 */
-
n = 255;
-
p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL); /* 分配n个struct probe */
-
if(p == NULL)
-
return -ENOMEM;
-
for(i = 0; i < n; i++, p++) { /* 用函数的参数初始化probe */
-
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;
-
}
dev_t的前12位为主设备号,后20位为次设备号。n = MAJOR(dev + range - 1) - MAJOR(dev) + 1 表示设备号范围(dev, dev+range)中不同的主设备号的个数。
通常n的值为1。从代码中的第二个for循环可以看出kobj_map中的probes数组中每个元素为一个struct probe链表的头指针。每个链表中的probe对象有(MAJOR(probe.dev) % 255)值相同的关系。若主设备号小于255, 则每个链表中的probe都有相同的主设备号。链表中的元素是按照range值从小到大排列的。
while循环即是找出该将p插入的位置。
kobj_unmap函数:
-
void kobj_unmap(struct kobj_map *domain, dev_t dev, unsigned long range)
-
{
-
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
-
unsigned index = MAJOR(dev);
-
unsigned i;
-
struct probe *found = NULL;
-
-
if (n > 255)
-
n = 255;
-
-
mutex_lock(domain->lock);
-
for (i = 0; i < n; i++, index++) {
-
struct probe **s;
-
for (s = &domain->probes[index % 255]; *s; s = &(*s)->next) {
-
struct probe *p = *s;
-
if (p->dev == dev && p->range == range) {
-
*s = p->next;
-
if (!found)
-
found = p;
-
break;
-
}
-
}
-
}
-
mutex_unlock(domain->lock);
-
kfree(found);
-
}
在16行,找到对应设备号dev和range指定的probe对象后,退出,然后kfree释放空间。
kobj_lookup函数:
-
struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index)
-
{
-
struct kobject *kobj;
-
struct probe *p;
-
unsigned long best = ~0UL;
-
-
retry:
-
mutex_lock(domain->lock);
-
for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) {
-
struct kobject *(*probe)(dev_t, int *, void *);
-
struct module *owner;
-
void *data;
-
-
if (p->dev > dev || p->dev + p->range - 1 < dev)
-
continue;
-
if (p->range - 1 >= best)
-
break;
-
if (!try_module_get(p->owner))
-
continue;
-
owner = p->owner;
-
data = p->data;
-
probe = p->get;
-
best = p->range - 1;
-
*index = dev - p->dev; /* 这个是用来干嘛的? */
-
if (p->lock && p->lock(dev, data) < 0) {
-
module_put(owner);
-
continue;
-
}
-
mutex_unlock(domain->lock);
-
kobj = probe(dev, index, data);
-
/* Currently ->owner protects _only_ ->probe() itself. */
-
module_put(owner);
-
if (kobj)
-
return kobj;
-
goto retry;
-
}
-
mutex_unlock(domain->lock);
-
return NULL;
-
}
对cdev_add函数,这里的p->probe函数即是exact_match, p->lock为exact_lock函数。
kobj_map_init函数:
-
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部分。
文件:
cdev.h
-
struct cdev {
-
struct kobject kobj;
-
struct module *owner;
-
const struct file_operations *ops;
-
struct list_head list;
-
dev_t dev;
-
unsigned int count;
-
}
-
void cdev_init(struct cdev *, const struct file_operations *);
-
struct cdev *cdev_alloc(void);
-
void cdev_put(struct cdev *p);
-
int cdev_add(struct cdev *, dev_t, unsigned);
-
void cdev_del(struct cdev *);
cdev_init函数,此函数首先调用kobject_init初始化cdev中的kobj,然后将cdev中的ops赋值。
cdev_alloc函数,先kzalloc分配一个cdev,然后用kobject_init初始化kobj
cdev_put函数:
-
void cdev_put(struct cdev *p)
-
{
-
if (p) {
-
struct module *owner = p->owner;
-
kobject_put(&p->kobj);
-
module_put(owner);
-
}
-
}
此函数调用kobject_put和module_put,好像它们的作用就是减少引用计数
cdev_add函数:
-
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);
-
}
主要是调用kobj_map将cdev放入cdev_map中。
cdev_del函数:
-
static void cdev_unmap(dev_t dev, unsigned count)
-
{
-
kobj_unmap(cdev_map, dev, count);
-
}
-
-
void cdev_del(struct cdev *p)
-
{
-
cdev_unmap(p->dev, p->count);
-
kobject_put(&p->kobj);
-
}
这就不用说啥了。
LDD3上说“只要cdev_add返回了,我们的设备就‘活’了,它的操作就会被内核调用",那么这句奇妙的话到底是个什么意思?下面是我目前了解的情况,据说在open一个字符设备文件时,最终总会调用chrdev_open。下面是该函数的源码,注意inode->i_rdev中保存了设备编号,inode->icdev指向了cdev结构
-
static int chrdev_open(struct inode *inode, struct file *filp)
-
{
-
struct cdev *p;
-
struct cdev *new = NULL;
-
int ret = 0;
-
-
spin_lock(&cdev_lock);
-
p = inode->i_cdev;
-
if (!p) {
-
struct kobject *kobj;
-
int idx;
-
spin_unlock(&cdev_lock);
-
kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
-
if (!kobj)
-
return -ENXIO;
-
new = container_of(kobj, struct cdev, kobj); /* 找到字符设备的cdev */
-
spin_lock(&cdev_lock);
-
/* Check i_cdev again in case somebody beat us to it while
-
we dropped the lock. */
-
p = inode->i_cdev;
-
if (!p) {
-
inode->i_cdev = p = new;
-
list_add(&inode->i_devices, &p->list);/* ZXG: 这是啥? */
-
new = NULL;
-
} else if (!cdev_get(p))
-
ret = -ENXIO;
-
} else if (!cdev_get(p))
-
ret = -ENXIO;
-
spin_unlock(&cdev_lock);
-
cdev_put(new);
-
if (ret)
-
return ret;
-
-
ret = -ENXIO;
-
filp->f_op = fops_get(p->ops);
-
if (!filp->f_op)
-
goto out_cdev_put;
-
-
if (filp->f_op->open) {
-
ret = filp->f_op->open(inode, filp); /* 调用cdev->ops中的open函数 */
-
if (ret)
-
goto out_cdev_put;
-
}
-
-
return 0;
-
-
out_cdev_put:
-
cdev_put(p);
-
return ret;
-
}
所以,简单地说,设备驱动程序通过调用cdev_add把它所管理的设备对象的指针嵌入到一个类型为struct probe的节点之中,然后再把该节点加入到cdev_map所实现的哈希链表中。
对系统而言,当设备驱动程序成功调用了cdev_add之后,就意味着一个字符设备对象已经加入到了系统,在需要的时候,系统就可以找到它。对用户态的程序而言,cdev_add调用之后,就已经可以通过文件系统的接口呼叫到我们的驱动程序。
阅读(2118) | 评论(0) | 转发(0) |