linux字符设备完全分析(一)

来源:互联网 发布:编程算法有哪些 编辑:程序博客网 时间:2024/06/07 21:11

终于。。。
我开始写博客了。。。。。
因为懒。。。所以拖了很久。。。。。
以后要多坚持。。。。。。。。。

今天主要是linux 字符设备分析,既然是分析,那就要往深了分析才有意思。。

字符设备的表示当然是指这个struct cdev结构。let us see。。。

struct cdev {    struct kobject kobj;    struct module *owner;    const struct file_operations *ops;    struct list_head list;    dev_t dev;    unsigned int count;};

kobj用来表示sys下的目录,owner模块使用者指针,一般赋值为THIS_MODULE,第三个参数很关键file_operations,是用户层调用open、release、write、read函数时。传到内核层处理,第四个结构list表示的是一个链表节点。第五个是设备号,没什么讲的。第六个是count

怎么分配结构体cdev呢,有两种方式,一种是静态定义一个 struct cdev my_cdev,然后在使用cdev_init函数去初始化该结构,第二种方式是利用cdev_alloc函数去动态申请。
有的人会先使用cdev_alloc函数去动态分配,在使用函数cdev_init去释放,其实这种用法是不太正确的,为什么呢?且看下面分析。

void cdev_init(struct cdev *cdev, const struct file_operations *fops){    memset(cdev, 0, sizeof *cdev);    INIT_LIST_HEAD(&cdev->list);    kobject_init(&cdev->kobj, &ktype_cdev_default);    cdev->ops = fops;}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;}

看出相同之处和不同之处了吗,yes kobject_init这个函数是关键,关键在于ktype类型不同、

static struct kobj_type ktype_cdev_default = {    .release    = cdev_default_release,};static struct kobj_type ktype_cdev_dynamic = {    .release    = cdev_dynamic_release,};

很明显,再看

static void cdev_default_release(struct kobject *kobj){    struct cdev *p = container_of(kobj, struct cdev, kobj);    cdev_purge(p);}static void cdev_dynamic_release(struct kobject *kobj){    struct cdev *p = container_of(kobj, struct cdev, kobj);    cdev_purge(p);    kfree(p);}

对,多了一个kfree,也就是说alloc申请出来的cdev占用的内存空间可以在设备被卸载时自动释放。

下面来看linux 内核怎么管理字符设备号。
内核提供了两种方式,一种是alloc_chrdev_region另一个是register_chrdev_region,两个的底层实现如出一辙。下面来分析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(0, baseminor, count, name);

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) {//动态申请        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;    }    cd->major = major;    cd->baseminor = baseminor;    cd->minorct = minorct;    strlcpy(cd->name, name, sizeof(cd->name));    i = major_to_index(major);    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;//挂接到哈希表里面    *cp = cd;    mutex_unlock(&chrdevs_lock);    return cd;out:    mutex_unlock(&chrdevs_lock);    kfree(cd);    return ERR_PTR(ret);}

分析到这里,就该看一个有意思的数据结构了,也就是所谓的哈希表。

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

这就是哈希表结构,chrdevs是一个指针数组,每一个元素都存储着一个字符设备,一看是static类型的,就知道这个chrdevs的每一项初始化后都为NULL,当一个新的设备来了之后通过主设备号来挂接到相应的chrdevs里面,主设备号就是哈希表的表头,而冲突域就用next指针来挂接。比如我注册了一个major = 254 baseminor= 3 的一个设备和major = 254 baseminor= 6的一个设备,这时候chrdevs[254]就应该指向major= 254 baseminor= 3的char_device_struct结构然后该结构的next指针又指向 major= 254 baseminor= 6的char_device_struct结构。

原创粉丝点击