从文件到字符设备

    技术2025-03-17  43

    在/dev下面有很多设备,其中也有大家广为使用的字符设备,呢里面的文件是如何与字符设备挂钩的呢~  平时我们使用的open函数是如何动态加载字符设备的操作集的呢~ 下面就让我们慢慢剖析~ (以内核2.6.26为参考) 一. 首先是文件系统~   需要动态解析文件路径名  像/dev/ts0  在文件系统里分为3个部分  1./(根文件目录)  2.dev(根文件目录下的dev目录)  3.ts0(dev目录下的ts0文件) 我们使用open(“/dev/ts0”,”打开方式”)之后~  就进入了系统调用sys_open之中~,如下图左A所示~  调用一直深入到do_file_open中后开始真正的路径解析     现在只简单的讲述一下~  path_look_open这个函数用于路径解析与nameidata结构的赋值   nameidata_to_filp这个函数用于nameidata转换为file结构 在这些步骤里~  我只点出几步比较重要的赋值~ A.首先是path_lookup_open函数 1.在real_lookup中(/fs/namei.c): 520     result = dir->i_op->lookup(dir,dentry,nd);  //这一步是根据文件所在的文件系统调用相应的lookup函数~  我使用的文件系统为ext3~  则调用ext3_lookup 2.在ext3_iget中(/fs/ext3/inode.c): 检测文件标识inode->i_mode~  不为普通文件,目录及链接文件则判定为字符文件,接着调用init_special_inode函数 3.在init_special_inode中(/fs/inode.c): 继续判断文件标识符,我们关心的是判断为字符文件这一段 1427    inode->i_fop   =  &def_chr_fops; 1428    inode->i_rdev  =  rdev; 这里赋了操作集与设备号,为后面的设备查找判断埋下伏笔 B.现在path_lookup_open函数调用完成~ 进入到nameidata_to_filp函数中 1.        在__dentry_open中(/fs/open.c): 825           f->f_op = fops_get(inode->i_fop); //在这里进行赋值,f->f_op = &def_chr_fops,注意上文inode->i_fop中的赋值 832           if(!open && f->f_op)        //在调用__dentry_open时open赋值为空,所以!open为真 833                    open = f->f_op->open;    //在这里将open赋为chrdev_open 835           error = open(inode,f);         //这里调用chrdev_open 2.        在chrdev_open中(/fs/char_dev.v): 371                        kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);     //执行kobj_lookup函数,在cdev_map里寻找相应的inode->i_rdev设备 cdev_map是一个256个probe结构组成的数组,用于查找具有相应设备号的设备,如何查找这个结构,将在后面的文章中详细介绍 inode->i_rdev为设备号  在前面已经提示了   374                new = container_of(kobj, struct cdev, kobj);                //从kobj的位置倒算出cdev的内存地址.获得包含相应kobj的cdev 378     inode->i_cdev = p = new;                              //到这里p已经为我们要的设备cdev了 390                filp->f_op = fops_get(p->ops);                         //终于拿到了梦寐以求的cdev操作集,至此~  以后read,write操作都通过file->f_op直接与我们要的设备操作集挂钩了       二现在详细讲述cdev_map这个重要的结构cdev_map是管理linux中字符设备的数组,它的描述为static struct kobj_map *cdev_map;而kobj_map的相关代码在/drivers/base/map.c里struct kobj_map {        struct probe {                struct probe *next;          //同一大设备号的下一个设备                dev_t dev;                                 //设备的设备号                unsigned long range;        //设备的范围,例如某设备下有255个设备                struct module *owner;                      kobj_probe_t *get;                         //取得函数,用于取得私有结构中的kobj,将在kobj_map解释                int (*lock)(dev_t, void *);     //互斥锁                void *data;                //私有结构        } *probes[255];        struct mutex *lock;};这就是kobj_map的结构下面来看一下操作函数,首先是初始化函数kobj_map_init,这个函数在chrdev_init中调用(/fs/char_dev.c)cdev_map = kobj_map_init(base_probe, &chrdevs_lock);   //初始化cdev_mapstruct kobj_map *kobj_map_init(kobj_probe_t *base_probe, struct mutex *lock){        struct kobj_map *p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL);   //从内存中取得kobj_map        struct probe *base = kzalloc(sizeof(*base), GFP_KERNEL);                          //从内存中取得一个probe        int i;        if ((p == NULL) || (base == NULL)) {                kfree(p);                kfree(base);                return NULL;        }                                //检测分配是否成功        base->dev = 1;         base->range = ~0;   //~0为4294967295        base->get = base_probe;   //以上三项初始化probe        for (i = 0; i < 255; i++)                p->probes = base;    //将kobj_map中的256项probe均指向base        p->lock = lock;        return p;}初始完后的数组如下图

     

      然后是分配函数kobj_map,该函数负责插入一个包含私有数据的probe到数组里在添加字符设备的时候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);}其中比较重要的是exact_match这个函数,他用于提取私有数据中的kobjint 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;  //计算需要多少个主设备号,range超过MIN则增加主设备号需要的数目        unsigned index = MAJOR(dev);                                          //取得主设备号        unsigned i;        struct probe *p;        if (n > 255)                        n = 255;      //主设备号大于255则限定在255        p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);     //分配主设备号数目需要的probe结构空间        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;        }                        //初始化取得的probe结构        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;                //寻找range适合的位置,算法为大的range在后面                p->next = *s;                *s = p;                                        //找到合适的位置则插入        }        mutex_unlock(domain->lock);        return 0;}插入一个probe后的数组如下图   关于kobj_map的题外话,在插入中我们可以看到,代码并不对设备号进行检测,也就说有可能有两个完全一样的设备号设备,如下图   LINUX并没有这样的检测机制~  只能全凭人工分配设备号的时候进行仔细的检查,也可能是我没发现其中的检测机制,望大家指出,万分感谢现在到了大家最关心的,如何使用设备号查找对应的设备呢?这个函数是kobj_lookup,它负责进行查询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)                        continue;     //跳过不在 p->dev  ----  p->dev+p->range  中的设备                if (p->range - 1 >= best)                        break;                if (!try_module_get(p->owner))                        continue;                owner = p->owner;                data = p->data;        //取得私有数据                probe = p->get;        //取得probe函数,用于提取私有数据中的kobj                best = p->range - 1;                *index = dev - p->dev;                   if (p->lock && p->lock(dev, data) < 0) {                        module_put(owner);                        continue;                }                mutex_unlock(domain->lock);                kobj = probe(dev, index, data);  //提取私有数据中的kobj                /* Currently ->owner protects _only_ ->probe() itself. */                module_put(owner);                if (kobj)                           return kobj;     //返回kobj                goto retry;        }        mutex_unlock(domain->lock);        return NULL;}关于kobj_unmap我就不说先了~转载请注明转自个人BLOG http://blog.chinaunix.net/u1/57901/
    最新回复(0)