2013年(5)
分类: LINUX
2013-07-31 13:43:48
在/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_map struct 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这个函数,他用于提取私有数据中的kobj 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; //计算需要多少个主设备号,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; } |