大家好,欢迎来到IT知识分享网。
本篇文章以mknod创建字符设备文件进行讲解
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
,以及将主设备号和次设备号形成的设备号保存在inode
的i_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()
函数进行下一步的处理,在这个函数中我们可以看到几个主要流程步骤:
filename_create()
函数初始化了一个struct dentry
还有一个struct path
,其中dentry
表示当前/dev/hello
的信息,而path
则表示/dev
的路径信息。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)等于truedev_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()
函数可以得到几个关键步骤:
inode = new_inode(sb);
这里会分配一个inode
- 如果分配到
inode
,则会对inode
进行一系列的初始化设置 - 最后由
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. 总结
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/127423.html