欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

linux字符设备解析

程序员文章站 2022-04-12 21:41:55
字符设备的表示当然是指这个struct cdev结构。let us see。 struct cdev { struct kobject kobj; str...

字符设备的表示当然是指这个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结构。