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

Linux字符设备驱动模型--字符设备的注册

程序员文章站 2022-07-14 10:53:48
...

当我们编写字符设备驱动程序的时候,在进行字符设备对象cdev的分配、初始化,设备号的注册这些初始化阶段之后,就可以将它加入到系统中,这样才能使用这个字符设备。将一个字符设备加入到系统中调用的函数时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);
}

其中,p为要加入到系统中的字符设备对象的指针(是分配好且初始化好的),dev为改设备的设备号,count为从设备的个数。
cdev_add的核心功能是通过kobj_map函数实现,后者是通过全局变量cdev_map来把设备p加入到其中的哈希表中。
struct 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];//255个元素的指针数组
    struct mutex *lock;
};

一下为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(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;
}

kobj_map函数中哈希表的实现原理和之前介绍的设备号的注册分配几乎完全一样,通过要加入系统的设备的主设备号major(major=MAJOR(dev))来获得probes数组的索引值i(i=major%255),然后把一个类型为struct probe的节点对象加入到probe[i]所管理的链表中,如下图:
Linux字符设备驱动模型--字符设备的注册
其中,struct probe 所在的矩形块中的深色部分是我们重点关系的内容,记录了当前正在加入系统的字符设备对象的有关信息。dev为他的设备号,range是次设备的个数,data是void*类型的指针,指向当前正要加入系统的设备对象指针p。上图中展示的是两个满足主设备号major%255=2的字符设备通过cdev_add之后kobj_map所展现出来的数据结构状态。

总结来说,设备驱动程序通过调用cdev_add把它管理的设备对象的指针嵌入到一个类型为struct probe的节点之后,然后在把该节点加入到cdev_map所实现的哈希链表中。

对于系统而言,当系统成功调用了cdev_add之后,就意味着一个字符设备对象已经加入到系统,在需要的时候,系统就可以找到它。而对于用户态的程序而言,cdev_add调用之后,就可以通过文件系统接口呼叫到我们的驱动程序。

最后,介绍下与cdev_add相对的cdev_del,这个函数的作用是:在cdev_add中我们动态分配了struct probe类型的节点,那么当对应的设备从系统中移除时,利用此函数将它们从链表中删除并释放节点所占用的内存空间。
源码如下:
cdev_del

void cdev_del(struct cdev *p)
{
    cdev_unmap(p->dev, p->count);
    kobject_put(&p->kobj);
}

此函数调用cdev_unmap,源码如下:

static void cdev_unmap(dev_t dev, unsigned count)
{
    kobj_unmap(cdev_map, dev, count);
}

调用的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);
}

对于以内核模块形式存在的驱动程序,作为通用的规则,模块的卸载函数应该负责调用这个函数来将锁管理的设备对象从系统之移除。