Linux中mknod命令实现原理以及源码分析

本篇文章以mknod创建字符设备文件进行讲解

字符设备驱动的Demo例子可参考该篇文章
Linux 编写简单驱动并测试

1. mknod 命令

mknod /dev/hello c 520 0

该命令主要通过制定要创建的设备文件名称/dev/hello,以及设备类型c字符设备,最后的520 0 表示为主设备号和次设备号。
当我们使用该命令创建好了/dev/hello设备文件,当我们在用户态对该文件进行open()write()read()时,就会调用到/dev/hello设备文件对应的设备驱动的file_operations对应的.open.write.read回调函数。
那么这一切是怎么做到的呢?这就跟我们的mknod命令的实现有关。

2. mknod 原理

当我们输入mknod命令时,实际上会创建设备文件/dev/hello和所对应的inode,以及将主设备号和次设备号形成的设备号保存在inodei_rdev中。

3. mknod 源码分析

已经知道mknode主要是为设备文件创建inode的原理后,我们来看一下代码是如何实现的。

3.1 mknod

SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, dev)
{
	return do_mknodat(AT_FDCWD, getname(filename), mode, dev);
}

这个是mknod命令的系统调用,我们键入的命令mknod /dev/hello c 520 0,将会为这个函数进行赋值,赋值的内容如下:

filename = "/dev/hello";
mode = mode | S_IFCHR;
dev = MKDEV(520, 0);

3.2 do_mknodat

static int do_mknodat(int dfd, struct filename *name, umode_t mode,
		unsigned int dev)
{
	struct user_namespace *mnt_userns;
	struct dentry *dentry;
	struct path path;
	int error;
	unsigned int lookup_flags = 0;
	error = may_mknod(mode);
	if (error)
		goto out1;
retry:
	dentry = filename_create(dfd, name, &path, lookup_flags);
	error = PTR_ERR(dentry);
	if (IS_ERR(dentry))
		goto out1;

	if (!IS_POSIXACL(path.dentry->d_inode))
		mode &= ~current_umask();
	error = security_path_mknod(&path, dentry, mode, dev);
	if (error)
		goto out2;

	mnt_userns = mnt_user_ns(path.mnt);
	switch (mode & S_IFMT) {
		case 0: case S_IFREG:
			error = vfs_create(mnt_userns, path.dentry->d_inode,
					   dentry, mode, true);
			if (!error)
				ima_post_path_mknod(mnt_userns, dentry);
			break;
		case S_IFCHR: case S_IFBLK:
			error = vfs_mknod(mnt_userns, path.dentry->d_inode,
					  dentry, mode, new_decode_dev(dev));
			break;
		case S_IFIFO: case S_IFSOCK:
			error = vfs_mknod(mnt_userns, path.dentry->d_inode,
					  dentry, mode, 0);
			break;
	}
out2:
	done_path_create(&path, dentry);
	if (retry_estale(error, lookup_flags)) {
		lookup_flags |= LOOKUP_REVAL;
		goto retry;
	}
out1:
	putname(name);
	return error;
}

mknod()函数调用到do_mknodat()函数进行下一步的处理,在这个函数中我们可以看到几个主要流程步骤:

  1. filename_create()函数初始化了一个struct dentry还有一个struct path,其中dentry表示当前/dev/hello的信息,而path则表示/dev的路径信息。
  2. vfs_mknod()该函数是最主要的处理部分。由于我们输入的mknod /dev/hello c 520 0命令会让mode & S_IFCHR情况成立,所以会进入到vfs_mknod()创建inode流程。

3.3 vfs_mknod

int vfs_mknod(struct user_namespace *mnt_userns, struct inode *dir,
	      struct dentry *dentry, umode_t mode, dev_t dev)
{
	bool is_whiteout = S_ISCHR(mode) && dev == WHITEOUT_DEV;
	int error = may_create(mnt_userns, dir, dentry);
	if (error)
		return error;

	if ((S_ISCHR(mode) || S_ISBLK(mode)) && !is_whiteout &&
	    !capable(CAP_MKNOD))
		return -EPERM;

	if (!dir->i_op->mknod)
		return -EPERM;

	error = devcgroup_inode_mknod(mode, dev);
	if (error)
		return error;

	error = security_inode_mknod(dir, dentry, mode, dev);
	if (error)
		return error;

	error = dir->i_op->mknod(mnt_userns, dir, dentry, mode, dev);
	if (!error)
		fsnotify_create(dir, dentry);
	return error;
}
EXPORT_SYMBOL(vfs_mknod);

vfs_mknod()函数是创建inode的公共流程函数,现在可以分析一下传入的主要参数信息:

  • struct inode *dir/dev目录所指向的inode信息
  • struct dentry *dentry/dev/hello我们要创建的设备文件的dentry
  • umode_t mode:该mode是指的我们要创建的设备文件类型,如我们键入的mknod /dev/hello c 520 0命令,所以我们要创建的是字符设备,也就是说S_ISCHR(mode)等于true
  • dev_t dev:和开始进入mknod系统调用一样都是,dev = MKDEV(520, 0)
    vfs_mknod()函数来看,最终会由/dev所指向的inode中的i_op中的mknod回调函数进行处理,因此到这里算是分析一部分了。
    而我们的/dev所指向的inode中的i_op中的mknod回调函数到底是什么呢?
    答案:shmem_mknod()函数,具体为什么?是因为/dev是一个目录,这个目录对应的文件系统是devtmpfs,而这个devtmpfs文件系统所使用的struct inode_operations操作是shmem_dir_inode_operations,因此会调用到shmem_mknod()函数。

3.4 shmem_mknod

static int
shmem_mknod(struct user_namespace *mnt_userns, struct inode *dir,
	    struct dentry *dentry, umode_t mode, dev_t dev)
{
	struct inode *inode;
	int error = -ENOSPC;
	inode = shmem_get_inode(dir->i_sb, dir, mode, dev, VM_NORESERVE);
	if (inode) {
		error = simple_acl_create(dir, inode);
		if (error)
			goto out_iput;
		error = security_inode_init_security(inode, dir,
						     &dentry->d_name,
						     shmem_initxattrs, NULL);
		if (error && error != -EOPNOTSUPP)
			goto out_iput;

		error = 0;
		dir->i_size += BOGO_DIRENT_SIZE;
		dir->i_ctime = dir->i_mtime = current_time(dir);
		d_instantiate(dentry, inode);
		dget(dentry); /* Extra count - pin the dentry in core */
	}
	return error;
out_iput:
	iput(inode);
	return error;
}

shmem_mknod()调用到shmem_get_inode()函数完成最终的/dev/hello设备文件所对应的inode的创建。

3.4 shmem_get_inode

static struct inode *shmem_get_inode(struct super_block *sb, const struct inode *dir,
				     umode_t mode, dev_t dev, unsigned long flags)
{
	struct inode *inode;
	struct shmem_inode_info *info;
	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
	ino_t ino;
	if (shmem_reserve_inode(sb, &ino))
		return NULL;

	inode = new_inode(sb);
	if (inode) {
		inode->i_ino = ino;
		inode_init_owner(&init_user_ns, inode, dir, mode);
		inode->i_blocks = 0;
		inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);
		inode->i_generation = prandom_u32();
		info = SHMEM_I(inode);
		memset(info, 0, (char *)inode - (char *)info);
		spin_lock_init(&info->lock);
		atomic_set(&info->stop_eviction, 0);
		info->seals = F_SEAL_SEAL;
		info->flags = flags & VM_NORESERVE;
		info->i_crtime = inode->i_mtime;
		INIT_LIST_HEAD(&info->shrinklist);
		INIT_LIST_HEAD(&info->swaplist);
		simple_xattrs_init(&info->xattrs);
		cache_no_acl(inode);
		mapping_set_large_folios(inode->i_mapping);

		switch (mode & S_IFMT) {
		default:
			inode->i_op = &shmem_special_inode_operations;
			init_special_inode(inode, mode, dev);
			break;
		case S_IFREG:
			inode->i_mapping->a_ops = &shmem_aops;
			inode->i_op = &shmem_inode_operations;
			inode->i_fop = &shmem_file_operations;
			mpol_shared_policy_init(&info->policy,
						 shmem_get_sbmpol(sbinfo));
			break;
		case S_IFDIR:
			inc_nlink(inode);
			/* Some things misbehave if size == 0 on a directory */
			inode->i_size = 2 * BOGO_DIRENT_SIZE;
			inode->i_op = &shmem_dir_inode_operations;
			inode->i_fop = &simple_dir_operations;
			break;
		case S_IFLNK:
			/*
			 * Must not load anything in the rbtree,
			 * mpol_free_shared_policy will not be called.
			 */
			mpol_shared_policy_init(&info->policy, NULL);
			break;
		}

		lockdep_annotate_inode_mutex_key(inode);
	} else
		shmem_free_inode(sb);
	return inode;
}

shmem_get_inode()函数可以得到几个关键步骤:

  1. inode = new_inode(sb);这里会分配一个inode
  2. 如果分配到inode,则会对inode进行一系列的初始化设置
  3. 最后由init_special_inode完成对inode最后的设置

3.5 init_special_inode

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 = &pipefifo_fops;
	else if (S_ISSOCK(mode))
		;	/* leave it no_open_fops */
	else
		printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"
				  " inode %s:%lu\n", mode, inode->i_sb->s_id,
				  inode->i_ino);
}
EXPORT_SYMBOL(init_special_inode);

init_special_inode()函数可以看出来,当执行mknod /dev/hello c 520 0命令为/dev/hello设备文件生成的inode时,由于指定的设备文件类型为字符类型,所以会为inode进行如下赋值操作:

if (S_ISCHR(mode)) {
		inode->i_fop = &def_chr_fops;
		inode->i_rdev = rdev;
}

4. 总结

到这里对于Linux中mknod命令实现原理以及源码分析已经结束。
我们执行mknod命令最终只是生成一个/dev/hello设备文件,而且为该设备文件生成对应的inode信息而已。
至于这个inode的信息如何使用,请看下一章节:
Linux中open命令实现原理以及源码分析
谢谢大家浏览,本博客为博主原创,未经允许不可转载。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值