您的位置:首页 > 运维架构 > Linux

Linux之dev详解

2015-11-21 18:54 399 查看
一.前言

以前对于cdev仅仅是知其然,而不知其所以然。在本文中,将深入理解cdev的架构以及具体的实现。

二.真实的cdev

2.1 设备号

搞驱动的都应该知道的东西,在写gpio驱动时,往往会用到以下两个函数。

alloc_chrdev_region     --自动分配设备号

register_chrdev_region  --分配以设定的设备号。

上面两个函数的调用很简单,当时却没有深入去理解其实现的原理,只知道其采用了hash表,但是具体怎么实现的却不知道。在这里来好好理解一下。上面两个函数的核心是__register_chrdev_region;那来看源码的实现。

static struct char_device_struct {

    struct char_device_struct *next;

    unsigned int major;

    unsigned int baseminor;

    int minorct;

    char name[64];

    struct cdev *cdev;        /* will die */

} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];   --CHRDEV_MAJOR_HASH_SIZE = 255

你看这个结构体会发现,其定义了一个指针数组,大小为255.

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

    if (cd == NULL)

        return ERR_PTR(-ENOMEM);

    mutex_lock(&chrdevs_lock);

    /* temporary */

    if (major == 0) {            --当调用alloc_chrdev_region时,传入的major为0,从表面上看调用alloc_chrdev_region会自动分配设备,但是有一个缺点就是其

                                   分配的设备号只能在255内,而且并没有使用hash表,从而大大减少了linux所支持的设备号数。

        for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {

            if (chrdevs[i] == NULL)

                break;

        }

        if (i == 0) {

            ret = -EBUSY;

            goto out;

        }

        major = i;

        ret = major;

    }

  --以下以register_chrdev_region为例即传入major不为0。

    cd->major = major;           

    cd->baseminor = baseminor;

    cd->minorct = minorct;

    strncpy(cd->name,name, 64);

    i = major_to_index(major);    -- major % CHRDEV_MAJOR_HASH_SIZE 这里采用除留余数法来产生地址。

    for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)   --这个是用于处理散列表的冲突。在这里采用拉链法来处理散列冲突。

        if ((*cp)->major > major ||((*cp)->major == major &&(((*cp)->baseminor >= baseminor) ||((*cp)->baseminor + (*cp)->minorct > baseminor))))

            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;                      --这里是将cd插入链表中。

    *cp = cd;

    mutex_unlock(&chrdevs_lock);

    return cd;

out:

    mutex_unlock(&chrdevs_lock);

    kfree(cd);

    return ERR_PTR(ret);

}

上面是分配设备号的核心函数,上面的描述比较空洞,那来看一个例子。

在两个驱动文件中都采用register_chrdev_region来分配设备号。

A文件.

dev_t devt = MKDEV(506,0); 

register_chrdev_region(devt,1,"A");

B文件.

dev_t devt = MKDEV(506,1); 

register_chrdev_region(devt,1,"B");

也许有人会奇怪这两个module怎么会用同一个主设备号,有人会认为是在同一个主设备号下有两个从设备,所以会采用同一个主设备号,其实不然,在命令行中输入cat /proc/devices 时会发现竟然有两个主设备号为506的设备,并且这设备号竟然大于255。到此时,你在回去去看cdev 设备号的hash表实现就不太难懂了。其实对于hash表的地址为506%255 = 251,而B的cd 等于 A的cd->next.就是hash出现地址冲突时采用拉链法来解决冲突的。

OK,到此对于cdev的设备号是如何分配的应该很清楚了吧。

2.2 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_alloc->cdev_init->cdev_add

那就按照上面的顺序来分别解释。

cdev_alloc和cdev_init都比较简单,这里就不说了。在cdev_add中有一个很重要的函数kobj_map。

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

}

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;

};

其传入的参数cdev_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; --当rang小于1<<20时,n=1

    unsigned index = MAJOR(dev);

    unsigned i;

    struct probe *p;

    if (n > 255)

        n = 255;

    p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);

    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;

    }

    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;

}

上面还是一个链表型的数组,用于保存module的信息。至于这些信息的作用哪儿有用,不急,且听我慢慢道来。

三.cdev打开

话说每一个设备在打开的时候都会使用open,而每一个cdev open的时候都会调用chrdev_open这个函数,而上面所说的cdev_add中的一些某明奇妙的东东都会在这里看到。

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;      --以此时p=NULL为例。

    if (!p) {

        struct kobject *kobj;

        int idx;

        spin_unlock(&cdev_lock);

        kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);   --查找kobject。这里面就知道kobj_mmap为什么那么做了。

        if (!kobj)

            return -ENXIO;

        new = container_of(kobj, struct cdev, kobj);    --根据kobj找到cdev

        spin_lock(&cdev_lock);

        p = inode->i_cdev;

        if (!p) {

            inode->i_cdev = p = new;

            inode->i_cindex = idx;

            list_add(&inode->i_devices, &p->list);

            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);  --将file的f_op用cdev自己的ops代替。

    if (!filp->f_op)

        goto out_cdev_put;

    if (filp->f_op->open) {     --这里将会调用设备自己的open。

        ret = filp->f_op->open(inode,filp);

        if (ret)

            goto out_cdev_put;

    }

    return 0;

 out_cdev_put:

    cdev_put(p);

    return ret;

}

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)  --还记否上面是根据什么来将probe节点插入到以probes[x]为头的链表中的。

            continue;

        if (p->range - 1 >= best)     --我感觉这个错误时不会发生的。

            break;

        if (!try_module_get(p->owner))  --判断module是否在内核中,在就增加计数。否则返回0表示module不在kernel中。

            continue;

        owner = p->owner;               --这里就是cdev->owner

        data = p->data;                 --data = cdev

        probe = p->get;                 --在cdev_add中就说明是exact_match

        best = p->range - 1;            --就是cdev_add中的参数count。

        *index = dev - p->dev;

        if (p->lock && p->lock(dev, data) < 0) { --p->lock = exact_lock

            module_put(owner);

            continue;

        }

        mutex_unlock(domain->lock);

        kobj = probe(dev, index, data);         --返回时cdev的kobject

                static struct kobject *exact_match(dev_t dev, int *part, void *data)

                {

                     struct cdev *p = data;

                    return &p->kobj;

                }

        /* Currently ->owner protects _only_ ->probe() itself. */

        module_put(owner);

        if (kobj)

            return kobj;

        goto retry;

    }

    mutex_unlock(domain->lock);

    return NULL;

}

四.总结

到此cdev的核心算是理解了,其理解有错之处请各位多多指教。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: