字符设备的注册(cdev_int()与cdev_add())

1.cdev_init()。初始化cdev对象的一些成员

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

首先把传进来的cdev对象所占的内存清零

然后初始化cdev->list_head链表

接着把cdev->kobj也初始化,调用的kobject_init注释如下:

void kobject_init(struct kobject *kobj, struct kobj_type *ktype)    //@kobj :要被初始化的kobject
{                                                                   //@ktype:该kobj要初始化的ktype成员
    char *err_str;

    if (!kobj) {                                                    //如果kobj为空
        err_str = "invalid kobject pointer!";                       //打印"invalid kobject pointer!"
        goto error;
    }
    if (!ktype) {
        err_str = "must have a ktype to be initialized properly!\n";//如果ktype为空
        goto error;                                                 //打印"must have a ktype to be initialized properly!\n"
    }
    if (kobj->state_initialized) {                                  //如果kobj已经被初始化过了
        /* do not error out as sometimes we can recover */
        printk(KERN_ERR "kobject (%p): tried to init an initialized "   //打印错误信息:尝试初始化一个已经初始化过的kobject
               "object, something is seriously wrong.\n", kobj);
        dump_stack();                                                   //在控制台打印Oops信息
    }

    kobject_init_internal(kobj);                                    //kobj初始化
    kobj->ktype = ktype;                                            //把该kobj的ktype成员初始化为ktype
    return;                                                         //返回

error:
    printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);           //打印错误信息
    dump_stack();                                                   //在控制台打印Oops信息
}
EXPORT_SYMBOL(kobject_init);

其中的ktype_cdev_default就是kobject_init函数的第二个参数,ktype_cdev_default的定义如下:

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

kobj_type类型中的release()是在kobject释放时调用的,但是看了cdev_default_release的实现,发现并没有释放这个kobject,因为cdev不是动态创建的,注意看cdev_alloc,它在调用kobject_init的时候,kobj_type成员就变成了ktype_cdev_dynamic,它里面的release函数就释放了这个cdev对象。

接下来看cdev_default_release做了什么:

static void cdev_default_release(struct kobject *kobj)
{
    struct cdev *p = container_of(kobj, struct cdev, kobj);
    struct kobject *parent = kobj->parent;

    cdev_purge(p);
    kobject_put(parent);
}

首先获得了该kobj所在的cdev对象,然后获得了这个kobj的父对象。接着调用cdev_purge(p),这个cdev_purge()做了什么呢?

static void cdev_purge(struct cdev *cdev)
{
    spin_lock(&cdev_lock);
    while (!list_empty(&cdev->list)) {
        struct inode *inode;
        inode = container_of(cdev->list.next, struct inode, i_devices);
        list_del_init(&inode->i_devices);
        inode->i_cdev = NULL;
    }
    spin_unlock(&cdev_lock);
}

首先是上锁,然后一个while循环,循环的条件是cdev里面的list_head链表非空,循环体内,首先获得链表的下一个成员所在的inode对象,然后利用inode->i_devices(struct list_head类型),将该节点从所在的链表删除,最后把该inode的i_cdev赋空。这个while循环是将挂载在cdev的list_head链表的所有成员删除。

回到cdev_default_release,接下来调用了kobject_put(parent),这一操作是将该kobj的父对象的kref减一,如果kref==0,调用parent的kobj_type成员中的release函数将parent删除。

2.cdev_add()

       cdev_add函数主要是将cdev加入到cdev_map中,然后将cdev的kobject成员的parent对象的kref成员加1(有点绕),具体函数实现如下:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
    int error;

    p->dev = dev;
    p->count = count;

    error = kobj_map(cdev_map, dev, count, NULL,
             exact_match, exact_lock, p);
    if (error)
        return error;

    kobject_get(p->kobj.parent);

    return 0;
}

该函数首先用设备号dev和count给p的成员赋值,接着调用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;

    p = kmalloc_array(n, sizeof(struct probe), 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;
}

首先是kobj_map成员,内核定义了一个kobj_map类型的对象cdev_map。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;
    } *probes[255];
    struct mutex *lock;
};

probes也是一个哈希表,实现原理和设备号的哈希表chrdevs几乎完全一样。下面看具体实现。

       首先计算主设备号个数,和register_chrdev_region中一样,防止range过大,分配的主设备号不止一个。然后定义一个index等于主设备号,申明一个struct probe类型的指针,调用kmalloc_array为p分配内存,如果p还为空,返回错误代码,接下来用传入的参数为p的成员初始化。接着调用cdev_map中的互斥锁上锁,接下来把p插入哈希表probes中,插入规则还是range小的在前。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: cdev_add函数是Linux内核中的一个函数,用于向系统注册字符设备。其函数原型如下: ```c int cdev_add(struct cdev *p, dev_t dev, unsigned int count); ``` 函数参数的含义如下: 1. `struct cdev *p`:要注册字符设备cdev结构体指针。 2. `dev_t dev`:设备号,表示设备的主设备号和次设备号。可以使用`MKDEV()`宏将主设备号和次设备号组合成设备号。 3. `unsigned int count`:设备的次设备号数量。如果只有一个设备,则该参数为1。 函数返回值为0表示成功,其他值表示失败。 ### 回答2: cdev_add函数是在Linux内核中用于向系统注册字符设备的函数。该函数有两个参数: 1. struct cdev *cdev:表示要注册字符设备结构体指针。这个结构体表示了一个字符设备,并包含了字符设备的各种属性和操作方法。 2. dev_t dev:表示要分配给设备的设备号。设备号由主设备号和次设备号组成,用于唯一标识一个设备。主设备号用于确定设备所属驱动程序,次设备号用于区分同一驱动程序下的不同设备。 通过调用cdev_add函数,可以将字符设备结构体与设备号关联起来,并将其注册到系统中。注册后,系统就可以通过设备号来识别并操作该设备。 同时,cdev_add函数还会负责将字符设备添加到系统的字符设备链表中,并完成相关的初始化工作,如为设备分配空间、初始化字符设备结构体等。这样,系统就可以通过字符设备链表来管理所有已注册字符设备,并提供相应的操作接口。 总结起来,cdev_add函数的参数含义是:要注册字符设备结构体指针和设备号,通过这个函数将字符设备注册到系统中,并完成相关的初始化工作,以便系统可以对该设备进行操作。 ### 回答3: cdev_add函数是Linux内核中的一个函数,主要用于向系统注册一个字符设备驱动,并将该驱动关联到一个主设备号和次设备号。 cdev_add函数的参数含义如下: 1. struct cdev *p:指向一个表示字符设备的struct cdev结构体的指针。该结构体是字符设备驱动的核心数据结构,其中包含了字符设备驱动的相关信息。 2. dev_t dev:表示设备号的数据类型。dev_t数据类型是一个32位整数,其中高12位表示主设备号,低20位表示次设备号。dev参数即表示这个字符设备驱动的设备号。 3. unsigned int count:表示连续的设备号的数量,即表示连续的次设备号的数量。count参数用于指定驱动所支持的设备的数量。 cdev_add函数的调用过程会完成如下几个步骤: 1. 首先,cdev_add函数会将dev参数中的主设备号作为参数调用了cdev_alloc函数,用于创建并初始化一个struct cdev结构体对象。 2. 然后,cdev_add函数会将count参数中指定的数量的次设备号分配给字符设备驱动,同时将这些次设备号与先前创建的struct cdev结构体对象进行关联。 3. 最后,cdev_add函数会将这个字符设备的struct cdev结构体对象添加到内核中,使之成为字符设备子系统中的一个有效的字符设备。 总的来说,cdev_add函数的作用是将一个字符设备驱动注册到系统中,并为该驱动分配主设备号和次设备号,使之能够正常操作设备。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值