在/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中后开始真正的路径解析
下面就让我们慢慢剖析~ (以内核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后的数组如下图
在这些步骤里~ 我只点出几步比较重要的赋值~
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