从文件到字符设备

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


关于kobj_unmap我就不说先了~

转载请注明转自个人BLOG http://blog.chinaunix.net/u1/57901





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值