一场由mknod引发的扯淡

我们注册完字符、块驱动设备后,一般会用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,去查找字符驱动的表(其实是一个数组),然后就能定位到相应的驱动设备的操作。


本人水平有限,有些不足或者错误之处还望多多指出


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值