我们注册完字符、块驱动设备后,一般会用mknod去建立,应用层与驱动的管道。例如mtd的字符驱动,我们会用mknod /dev/mtdchar1 c 30 0,创建/dev/mtdchar1来映射mtd第一个字符设备。
由于代码没法红字,代码中有color:#ff6666 开头的行数,为重点分析的
接下来我们用代码来分析,这个过程。
第一个函数asmlinkage long sys_mknod(const char __user *filename, int mode, unsigned dev) //在fs/namei.c里,filename就是所创建的路径:/dev/mtdchar1,mode: S_IFCHR | S_IRUSR | S_IWUSR,这个意思是可读可写的字符特殊文件;dev为设备号,主设备号跟次设备号的结合体。
上面那个函数没什么可分析的,再下来进入 long sys_mknodat(int dfd, const char __user *filename, int mode,unsigned dev)
代码一:
asmlinkage long sys_mknodat(int dfd, const char __user *filename, int mode,
unsigned dev)
{
int error = 0;
char * tmp;
struct dentry * dentry;
struct nameidata nd;
if (S_ISDIR(mode))
return -EPERM;
tmp = getname(filename); //比较安全的一种方式把filename放到tmp所指的空间里
if (IS_ERR(tmp))
return PTR_ERR(tmp);
error = do_path_lookup(dfd, tmp, LOOKUP_PARENT, &nd); //nd->dentry为当前文件系统(rootfs)的主dentry
//当前fs下,也就是rootfs('/')
if (error)
goto out;
dentry = lookup_create(&nd, 0); //生成一个新的dentry
//dentry->d_parnet=nd.dentry
error = PTR_ERR(dentry);
if (!IS_POSIXACL(nd.dentry->d_inode))
mode &= ~current->fs->umask;
if (!IS_ERR(dentry)) {
switch (mode & S_IFMT) {
case 0: case S_IFREG:
error = vfs_create(nd.dentry->d_inode,dentry,mode,&nd);
break;
case S_IFCHR: case S_IFBLK:
error = vfs_mknod(nd.dentry->d_inode,dentry,mode,
new_decode_dev(dev)); //生成一个inode挂载到dentry
//inode->i_sb=nd.dentry->d_inode->i_sb inode->i_rdev=dev
break;
case S_IFIFO: case S_IFSOCK:
error = vfs_mknod(nd.dentry->d_inode,dentry,mode,0);
break;
case S_IFDIR:
error = -EPERM;
break;
default:
error = -EINVAL;
}
dput(dentry);
}
mutex_unlock(&nd.dentry->d_inode->i_mutex);
path_release(&nd);
out:
putname(tmp);
return error;
}
上面那个函数里字体加红的部分为重点部分。再进行分析之前,我们先要搞清楚,dentry,inode之间的关系,其实嘛很简单,就是dentry->d_inode=inode,就是通过dentry可以找到相对应的inode,至于inode里面的其他操作就各有风情。我个人是这么理解的,单细胞生物考虑问题就这么简单。
一:
下面进行那两个函数的分析。第一个error = do_path_lookup(dfd, tmp, LOOKUP_PARENT, &nd);第一眼看到这个函数,就应该知道这个函数主要作用是给nd做配置。
代码二:
static int fastcall do_path_lookup(int dfd, const char *name,
unsigned int flags, struct nameidata *nd)
{
int retval = 0;
int fput_needed;
struct file *file;
struct fs_struct *fs = current->fs;
nd->last_type = LAST_ROOT; /* if there are only slashes... */
nd->flags = flags;
nd->depth = 0;
if (*name=='/') { //表明在根目录下,那么对应的mnt和dentry很简单就是根目录的mnt和dentry
read_lock(&fs->lock);
if (fs->altroot && !(nd->flags & LOOKUP_NOALT)) {
nd->mnt = mntget(fs->altrootmnt);
nd->dentry = dget(fs->altroot);
read_unlock(&fs->lock);
if (__emul_lookup_dentry(name,nd))
goto out; /* found in altroot */
read_lock(&fs->lock);
}
nd->mnt = mntget(fs->rootmnt);
nd->dentry = dget(fs->root);
read_unlock(&fs->lock);
} else if (dfd == AT_FDCWD) {
read_lock(&fs->lock);
nd->mnt = mntget(fs->pwdmnt);
nd->dentry = dget(fs->pwd);
read_unlock(&fs->lock);
} else {
…………//此处省略1024,为了排版好看点
}
current->total_link_count = 0;
retval = link_path_walk(name, nd); //nd的dentry,inode,last
out:
if (likely(retval == 0)) {
if (unlikely(!audit_dummy_context() && nd && nd->dentry &&
nd->dentry->d_inode))
audit_inode(name, nd->dentry->d_inode);
}
out_fail:
return retval;
fput_fail:
fput_light(file, fput_needed);
goto out_fail;
}
题外话:客官或许对current->fs有疑问,这个我一开始也蛋疼了很久,其实嘛很简单,在start_kernel函数,也就是linux起来初始化的时候对其进行了初始化。start_kernel有个vfs_caches_init,vfs_caches_init里有个mnt_init,mnt里有个init_mount_tree,在init_mount_tree里有两行代码,对current->fs进行了赋值。
mnt = do_kern_mount("rootfs", 0, "rootfs", NULL); //ns->mnt为rootfs
set_fs_pwd(current->fs, ns->root, ns->root->mnt_root); //设置当前pwd为ns->root
//fs->pwdmnt=mnt fs->pwd=dentry
set_fs_root(current->fs, ns->root, ns->root->mnt_root);//设置当前root为ns->root
//fs->rootmnt=mnt fs->root=dentry
题外话完毕,上面那个函数的重点,是红字部分,也就是link_path_walk(name, nd);这个函数干嘛呢,看名字好像跟路径有关系,管它呢,接着一条道走到黑,进去再说。
代码三:
int fastcall link_path_walk(const char *name, struct nameidata *nd)
{
struct nameidata save = *nd;
int result;
/* make sure the stuff we saved doesn't go away */
dget(save.dentry); //保存信息,出错的时候可以纠正
mntget(save.mnt);
result = __link_path_walk(name, nd); //nd的dentry和mnt,last进行查找
//比如name为dev/console
//dentry,mnt均为dev的,last里保存console信息
if (result == -ESTALE) {
*nd = save;
dget(nd->dentry);
mntget(nd->mnt);
nd->flags |= LOOKUP_REVAL;
result = __link_path_walk(name, nd);
}
dput(save.dentry);
mntput(save.mnt);
return result;
}
这个函数看上去简单很多了,老规矩,接着红字部分
代码四:
static fastcall int __link_path_walk(const char * name, struct nameidata *nd)
{
.......//为了看上去短点,这里省略一些代码,具体可以对照namei.c
while (*name=='/') //如果是根目录的话,去掉根目录
name++;
inode = nd->dentry->d_inode;
/* At this point we know we have a real path component. */
for(;;) {
unsigned long hash;
struct qstr this;
unsigned int c;
nd->flags |= LOOKUP_CONTINUE;
err = exec_permission_lite(inode, nd); //可执行
this.name = name;
c = *(const unsigned char *)name;
hash = init_name_hash();
do { //一个路径一个路径的hash。比如dev/mtdchar1,先dev三个字符hash一下,去查找
name++;
hash = partial_name_hash(c, hash);
c = *(const unsigned char *)name;
} while (c && (c != '/'));
this.len = name - (const char *) this.name;
this.hash = end_name_hash(hash); //这个hash值不是this.name的,而是其中一个路径的,例如dev的
/* remove trailing slashes? */
if (!c)
goto last_component; //如果是最后一项了,比如已经到mtdchar1了
while (*++name == '/');
if (!*name)
goto last_with_slashes;
..........//省略一些代码
err = do_lookup(nd, &this, &next); //根据name.hash查找dentry和mnt,next指向这些
//比如/dev/mtdchar1 ,第一次这里先找到了dev的dentry和mnt
……//省略
if (inode->i_op->follow_link) {
err = do_follow_link(&next, nd);
inode = nd->dentry->d_inode;
} else
path_to_nameidata(&next, nd);//nd的mnt和dentry指向next的mnt和dentry
//然后nd的mnt和dentry也有了变化
continue; //翻转上去
/* here ends the main loop */
last_with_slashes:
lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
last_component:
nd->flags &= lookup_flags | ~LOOKUP_CONTINUE;
if (lookup_flags & LOOKUP_PARENT)
goto lookup_parent;
……//省略一部分代码
err = do_lookup(nd, &this, &next);
if (err)
break;
inode = next.dentry->d_inode;
if ((lookup_flags & LOOKUP_FOLLOW)
&& inode && inode->i_op && inode->i_op->follow_link) {
err = do_follow_link(&next, nd);
if (err)
goto return_err;
inode = nd->dentry->d_inode;
} else
path_to_nameidata(&next, nd);
err = -ENOENT;
if (!inode)
break;
if (lookup_flags & LOOKUP_DIRECTORY) {
err = -ENOTDIR;
if (!inode->i_op || !inode->i_op->lookup)
break;
}
goto return_base;
lookup_parent:
nd->last = this; //nd的last指向最后那个this?
//比如/dev/console的话,这个nd->last就指向了this,也就是name是console,相应的hash也是console
nd->last_type = LAST_NORM;
if (this.name[0] != '.')
goto return_base;
if (this.len == 1)
nd->last_type = LAST_DOT;
else if (this.len == 2 && this.name[1] == '.')
nd->last_type = LAST_DOTDOT;
else
goto return_base;
return_reval:
……//省略
return_base:
return 0;
out_dput:
dput_path(&next, nd);
break;
}
path_release(nd);
return_err:
return err;
}
上面这个函数太长了,裁掉一些懒得分析的,或者说太烦的,或者说不懂的,主要还是不懂的^_^。接下来函数功能分析,一开始假设我们进来的/dev/mtdchar1,是根目录,进来的第一件事是去掉根目录,剩下dev/mtdchar1,
然后进入一个大循环,这个为什么要大循环呢,作用就是把路径一层一层给剥掉。dev/mtdchar1剥成dev和mtdchar1,这个过程就是while (c && (c != '/'))这个小循环里干的,在这个小循环里将路径转换成hash,通过
err = do_lookup(nd, &this, &next);去查找dentry和mnt,也就是根据this里的hash值去nd里面查相对应的dentry,没有的话,创建一个,创建的dentry->d_parent=nd->dentry。关于dentry,后面有空再分析吧,比较烦的。
接下来path_to_nameidata(&next, nd);这个函数的功能一目了然,就是将next中的值赋予nd,这时候nd指向下一个路径。比如/dev/mtdchar1 ,第一进入大循环的时候,nd是根目录"/",那么经过这一步后,nd就是“dev”了,即nd->dentry->d_name.name=“dev”。continue,进入第二次大循环。
如果if (!c) goto last_component;//最后一项了。this.hash的值为mtdchar1。如果nd->flag==LOOKUP_PARENT的话,就进入lookup_parent,对了在很久很久之前,前到我边写边忘记,还得查查。sys_mknodat里的do_path_lookup,也就是第一段代码里,标志了LOOKUP_PARENT。直接就返回了,不干什么事。
这个函数这么长的一堆,最后return 0,它闲的蛋疼啊。我们再看看入口参数,对了它也在对nd这个参数进行修改。那好我们总结一下nd哪些东西变了,最最重要的变化,nd->dentry这个不再是根目录了,它变成了路径最后一个目录了,这里具体一点,nd->dentry->d_name.name=“dev”,这说明dentry再也不是刚进来的那个清纯少年了。还有nd->last,这个参数里保存了路径最后一个节点的信息。nd->last.name=“mtdchar1”,hash=hash(mtdchar1)。nd的flag跟last_type也有了变化。总之nd已有点面目全非,快要最终目的了。
走完代码四,我们又得反过头回到代码一种去。经过代码二、三、四,已经将代码一中的nd进行了赋值。
题外话:例如这里的nd->dentry->d_name.name=“dev”,nd->last.name=“mtdchar1”,hash=hash(mtdchar1)。
二:
接下来进入dentry = lookup_create(&nd, 0);这个的是生成一个新的dentry父节点是nd->dentry,dentry->d_name=nd->last。
代码五:
struct dentry *lookup_create(struct nameidata *nd, int is_dir)
{
struct dentry *dentry = ERR_PTR(-EEXIST);
mutex_lock_nested(&nd->dentry->d_inode->i_mutex, I_MUTEX_PARENT);
if (nd->last_type != LAST_NORM)
goto fail;
nd->flags &= ~LOOKUP_PARENT;
nd->flags |= LOOKUP_CREATE;
nd->intent.open.flags = O_EXCL;
/*
* Do the final lookup.
*/
dentry = lookup_hash(nd); //这里根据nd->last生成一个dentry挂到nd->dentry上
if (IS_ERR(dentry))
goto fail;
/*
* Special case - lookup gave negative, but... we had foo/bar/
* From the vfs_mknod() POV we just have a negative dentry -
* all is fine. Let's be bastards - you had / on the end, you've
* been asking for (non-existent) directory. -ENOENT for you.
*/
if (!is_dir && nd->last.name[nd->last.len] && !dentry->d_inode)
goto enoent;
return dentry;
enoent:
dput(dentry);
dentry = ERR_PTR(-ENOENT);
fail:
return dentry;
}
老规矩,红字搞起。
代码六:
static struct dentry *lookup_hash(struct nameidata *nd)//根据last的hash去查找nd->dentry上的节点,没有的话生成一个新的
{
return __lookup_hash(&nd->last, nd->dentry, nd);
}
再进去
代码七:
static struct dentry * __lookup_hash(struct qstr *name, struct dentry * base, struct nameidata *nd)
//根据name查找dentry,如果没有则生成一个,挂载base下面
{
struct dentry * dentry;
struct inode *inode;
int err;
inode = base->d_inode;
err = permission(inode, MAY_EXEC, nd);
dentry = ERR_PTR(err);
if (err)
goto out;
/*
* See if the low-level filesystem might want
* to use its own hash..
*/
if (base->d_op && base->d_op->d_hash) {
err = base->d_op->d_hash(base, name);
dentry = ERR_PTR(err);
if (err < 0)
goto out;
}
dentry = cached_lookup(base, name, nd); //根据name.hash在base上找dentry
if (!dentry) { //如果在base上没有的话
struct dentry *new = d_alloc(base, name); //生成一个dentry
//新dentry的d_parent为base,base->d_subdirs中存放dentry
dentry = ERR_PTR(-ENOMEM);
if (!new)
goto out;
dentry = inode->i_op->lookup(inode, new, nd);
if (!dentry)
dentry = new;
else
dput(new);
}
out:
return dentry;
}
上面代码里dentry是怎么产生呢,其实很一目了然了,先去根据name.hash去查找base下有没有对应的
子dentry,如果没有,就生成一个新的dentry,并且它的父dentry为base。父子dentry关系,子可以通过dentry->d_parent找到父;父可以通过dentry->d_subdirs链表找到子。父找到子的代码,可以参考下面的代码八。遍历父节点下所有的子节点。
代码八:
struct list_head *child;
struct dentry *de;
list_for_each(child,&this_dentry->d_subdirs){
de = list_entry(child,struct dentry,d_u.d_child);
//printk("de_name:%s\n",de->d_name.name);
}
好了,dentry = lookup_create(&nd, 0)也完成了,得到一个dentry,它的父节点是nd->dentry,实例化的话,就是生成一个dentry->d_name.name="mtdchar1"的dentry,dentry->d_parent->d_name.name=“dev”。
三:
接下来进入最后一步,因为我们的mode是S_IFCHR,所以调用error = vfs_mknod(nd.dentry->d_inode,dentry,mode,new_decode_dev(dev));
代码九:
int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
{
int error = may_create(dir, dentry, NULL);
if (error)
return error;
if ((S_ISCHR(mode) || S_ISBLK(mode)) && !capable(CAP_MKNOD))
return -EPERM;
if (!dir->i_op || !dir->i_op->mknod)
return -EPERM;
error = security_inode_mknod(dir, dentry, mode, dev);
if (error)
return error;
DQUOT_INIT(dir);
error = dir->i_op->mknod(dir, dentry, mode, dev); //调用相应文件系统的api
//例如调用ramfs/inode.c 中的mknod
//生成inode挂载到dentry上。
//inode->i_sb=dir->i_sb,inode->i_rdev=dev
if (!error)
fsnotify_create(dir, dentry);
return error;
}
红色部分分析,因为当前使用的文件系统是rootfs,所以在ramfs/inode.c中找到相应的mknod:ramfs_mknod
题外话:关于如何定位dir->i_op->mknod,发现在这个函数与dir有关系,也就是路径,不妨假设没有dev这个路径,没有这个路径mknod的时候会报错,所以要先mkdir这个路径,因为是在根目录下mkdir的,这个i_op->mknod肯定在根目录下。我们的根目录是roorfs,所以在ramfs/inode.c中很容易得到根目录的mknod为ramfs_mknod,再通过ramfs_get_inode得到新路径的inode->i_op。其中i_op->mknod为ramfs_mknod
代码十:
static int
ramfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
{
struct inode * inode = ramfs_get_inode(dir->i_sb, mode, dev); //得到一个新的inode
//sb跟inode的关系,inode->i_sb=sb sb->s_inddes链表中有inode->i_sb_list
//inode->i_rdev=dev 设备号
int error = -ENOSPC;
if (inode) {
if (dir->i_mode & S_ISGID) {
inode->i_gid = dir->i_gid;
if (S_ISDIR(mode))
inode->i_mode |= S_ISGID;
}
d_instantiate(dentry, inode); //将生成的inode挂至dentry上 dentry->d_inode=inode
dget(dentry); /* Extra count - pin the dentry in core */
error = 0;
dir->i_mtime = dir->i_ctime = CURRENT_TIME;
}
return error;
}
红色部分进去ramfs_get_inode为得到一个新的inode
代码十一:
struct inode *ramfs_get_inode(struct super_block *sb, int mode, dev_t dev)
{
struct inode * inode = new_inode(sb);
if (inode) {
inode->i_mode = mode;
inode->i_uid = current->fsuid;
inode->i_gid = current->fsgid;
inode->i_blocks = 0;
inode->i_mapping->a_ops = &ramfs_aops;
inode->i_mapping->backing_dev_info = &ramfs_backing_dev_info;
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
switch (mode & S_IFMT) {
default:
init_special_inode(inode, mode, dev); //挂设备号
break;
case S_IFREG:
inode->i_op = &ramfs_file_inode_operations;
inode->i_fop = &ramfs_file_operations;
break;
case S_IFDIR:
inode->i_op = &ramfs_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
/* directory inodes start off with i_nlink == 2 (for "." entry) */
inc_nlink(inode);
break;
case S_IFLNK:
inode->i_op = &page_symlink_inode_operations;
break;
}
}
return inode;
}
因为我们的mode不是路径也不是文件,所以进入default
代码十二:
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
if (S_ISCHR(mode)) {
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;
} else if (S_ISBLK(mode)) {
inode->i_fop = &def_blk_fops;
inode->i_rdev = rdev;
} else if (S_ISFIFO(mode))
inode->i_fop = &def_fifo_fops;
else if (S_ISSOCK(mode))
inode->i_fop = &bad_sock_fops;
else
printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o)\n",
mode);
}
根据上面加红部分,可以看到驱动设备号已经跟inode关联起来的。mknod的使命也完成了。
最后:
一些废话:在sys_open打开这个文件时,open = f->f_op->open,会根据inode->i_rdev,去查找字符驱动的表(其实是一个数组),然后就能定位到相应的驱动设备的操作。
本人水平有限,有些不足或者错误之处还望多多指出