linux设备:cdev和kobj_map

 

 

Linux kernel v3.6.7

先看kobj_map相关的代码

涉及到的文件

<linux/kobj_map.h>

<drivers/base/map.c>


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 *);

​​​​​​先看kobj_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; /* 指向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的核心就是一个struct probe指针类型、大小为255的数组,而在这个probe结构中,第一个成员next(21)显然是将这些probe结构通过链表的形式连接起来,dev_t类型的成员dev显然是设备号,get(25)和lock(26)分别是两个函数接口,最后的重点来了,void*作为C语言中的万金油类型,在这里就是我们cdev结构(通过后面的分析可以看出),所以,这个cdev_map是一个struct kobj_map类型的指针,其中包含着一个struct probe指针类型、大小为255的数组,数组的每个元素指向的一个probe结构封装了一个设备号和相应的设备对象(这里就是cdev),可见,这个cdev_map封装了系统中的所有的cdev结构和对应的设备号,最多为255个字符设备

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;

}

 

 

在初始化一个kobj_map对象时,将probes指针全部指向同一个base。

 

 

 

下面是cdev部分。

文件:

<linux/cdev.h>

<fs/char_dev.c>

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);

}
  1.  

 

这就不用说啥了。

 

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;

}

 

 

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Linux 内核中,cdev_map 是一个用于管理字符设备的哈希表。每个字符设备都有一个对应的 cdev 结构体,而 cdev_map 中的每个元素都是一个指向 cdev 结构体的指针。当内核启动时,会依次遍历 cdev_map 中的每个元素,并调用其中的 probe 函数,用于初始化每个字符设备。而 cdev_map 中元素的遍历顺序是按照哈希算法计算得到的值来确定的。 不同于 kobj_map 中按照内存地址范围升序排列,在 cdev_map 中,元素的遍历顺序是由哈希算法决定的,而且哈希算法的结果是不确定的,因此 cdev_map 中元素的排列顺序是随机的。这就需要 probe 函数必须能够处理不同的字符设备之间可能存在的依赖关系。 为了确保依赖关系得到正确处理,Linux 内核中字符设备的注册顺序是非常重要的。通常情况下,字符设备的注册顺序应该按照依赖关系来确定,即先注册被依赖的设备,再注册依赖的设备。为了实现这一点,Linux 内核中规定了一个约定:所有字符设备的主设备号必须是连续的,并且按照主设备号升序排列。在 cdev_map 中,元素的遍历顺序也要按照主设备号升序排列,这样才能保证依赖关系得到正确处理。 因此,cdev_map 中的 probe 函数按照主设备号升序排列,确保被依赖的字符设备的 probe 函数先于依赖的字符设备的 probe 函数调用,从而保证依赖关系得到正确处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值