linux设备:cdev和kobj_map

本文转自 http://blog.chinaunix.net/uid-24631445-id-3415459.html
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函数

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

这就不用说啥了。
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;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值