Linux VFS文件系统之打开(Open)文件

xiaoxiao2021-02-28  67

------------------------------------------------ #纯属个人理解,如有问题敬请谅解! #kernel version: 2.6.26 #Author: andy wang ------------------------------------------------- 一: 概述 文件的打开读写操作是一项复杂的工作, 本文只讨论VFS层系统调用打开文件的实现, 文件的打开操作流程大致是这样的: 首先在当前进程的文件描述表fdtale中分配一个空的文件描述符fd , 然后在filp_cachep中创建一个file struct , 调用do_path_lookup()找的文件的inode ,取出inode的文件操作方法file_operations赋给file struct ,并调用f->f_op->open()执行打开的操作. 最后根据根据文件描述符fd将file安装在当前进程文件描述表fdtable对应的位置.欧了,整个打开文件的流程大致就是这个样子的了. 这样在以后读写文件操作时, 只需要根据文件描述符fd,就可以在当前进程的描述表中找到该文件的file对象,然后调用f->f_op操作方法对文件进行操作. 二: open的实现流程 其实 VFS操作一个文件就是要找到该文件索引节点关联的文件操作方法. 下面就正式开始进入Open系统调用: 系统调用Open打开一个文件, kernel VFS层调用sys_open()->do_sys_open()执行打开文件. 下面看看代码片段: long do_sys_open(int dfd, const char __user *filename, int flags, int mode) { char *tmp = getname(filename); //文件路径名filename拷贝到内核空间 int fd = PTR_ERR(tmp); if (!IS_ERR(tmp)) { fd = get_unused_fd_flags(flags); //获取空的文件描述符fd if (fd >= 0) { //文件描述符为非负数 struct file *f = do_filp_open(dfd, tmp, flags, mode); //分配并初始化file结构 if (IS_ERR(f)) { put_unused_fd(fd); //释放fd fd = PTR_ERR(f); } else { s fsnotify_open(f->f_path.dentry); fd_install(fd, f); //安装file到当前进程描述表中 } } putname(tmp); } return fd; //返回文件描述符fd到用户空间. } 首先拷贝文件路径名字到内核空间, 我们需要这个路径在VFS中找到文件的dentry,然后取到inode指向的该文件的操作方法。接下来就是调用get_unused_fd_flags()在当前进程的文件描述表中取到未使用的文件描述符fd , 对于用户空间来说,文件描述符fd就像打开文件的一把钥匙;只要有了文件文件描述符fd ,我们就可以取出文件中的内容了. 接下来我们要看看打开的文件信息是如何与进程联系在一起的. 如下是do_filp_open()代码片段: struct file *do_filp_open(int dfd, const char *pathname, int open_flag, int mode) { struct file *filp; struct nameidata nd; int acc_mode, error; struct path path; struct dentry *dir; int count = 0; int will_write; int flag = open_to_namei_flags(open_flag); acc_mode = ACC_MODE(flag); /* O_TRUNC implies we need access checks for write permissions */ if (flag & O_TRUNC) acc_mode |= MAY_WRITE; /* Allow the LSM permission hook to distinguish append access from general write access. */ if (flag & O_APPEND) acc_mode |= MAY_APPEND; if (!(flag & O_CREAT)) { //这是打开文件最简单的一种情况 error = path_lookup_open(dfd, pathname, lookup_flags(flag), &nd, flag); //创建file,并查找文件,保存在nd中 if (error) return ERR_PTR(error); goto ok; } /* * Create - we need to know the parent. */ error = path_lookup_create(dfd, pathname, LOOKUP_PARENT, &nd, flag, mode); //文件不存在,首先需要创建文件 if (error) return ERR_PTR(error); ……………. ok: /* * Consider: * 1. may_open() truncates a file * 2. a rw->ro mount transition occurs * 3. nameidata_to_filp() fails due to * the ro mount. * That would be inconsistent, and should * be avoided. Taking this mnt write here * ensures that (2) can not occur. */ will_write = open_will_write_to_fs(flag, nd.path.dentry->d_inode); //返回是否需要判断写权限 if (will_write) { //如果是普通文件或者目录文件 error = mnt_want_write(nd.path.mnt); //判断写权限 if (error) goto exit; } error = may_open(&nd, acc_mode, flag); if (error) { if (will_write) mnt_drop_write(nd.path.mnt); goto exit; } filp = nameidata_to_filp(&nd, open_flag); //初始化file ,执行f-> f_op->open() /* * It is now safe to drop the mnt write * because the filp has had a write taken * on its behalf. */ if (will_write) mnt_drop_write(nd.path.mnt); return filp; } 这个函数比较长,所以只是列出了部分代码, 只讨论文件打开的一般情况. 我们首先来跟踪一下path_lookup_open()函数; 其调用关系为path_lookup_open()-> __path_lookup_intent_open() ,下面是__path_lookup_intent_open()代码片段: static int __path_lookup_intent_open(int dfd, const char *name, unsigned int lookup_flags, struct nameidata *nd, int open_flags, int create_mode) { struct file *filp = get_empty_filp(); //在filp_cachep中分配file int err; if (filp == NULL) return -ENFILE; nd->intent.open.file = filp; nd->intent.open.flags = open_flags; nd->intent.open.create_mode = create_mode; err = do_path_lookup(dfd, name, lookup_flags|LOOKUP_OPEN, nd); //查找文件 if (IS_ERR(nd->intent.open.file)) { if (err == 0) { err = PTR_ERR(nd->intent.open.file); path_put(&nd->path); } } else if (err != 0) release_open_intent(nd); return err; } 这个函数比较简单, 它就是将分配的file和查找文件的信息都保存在nd中, do_path_lookup()查找路径已经在上篇文章中介绍过了。 现在我们再回到do_filp_ope()函数中, 在执行完path_lookup_open()函数后 , 信息都保存在了nd中了.然后执行goto ok; 调用函数open_will_write_to_fs()判断该文件所在文件系统是否需要进行写权限的判断, 特殊文件是不需要进行写权限判断的, 而目录和普通文件是需要判断所在文件系统写权限的. 下面这个函数nameidata_to_filp()是一个很重要的函数,因为这个函数将我们要操作目标文件的操作方法初始化给了filep ,这样文件索引节点inode的使命就完成了. 下面是这个函数的代码片段: struct file *nameidata_to_filp(struct nameidata *nd, int flags) { struct file *filp; /* Pick up the filp from the open intent */ filp = nd->intent.open.file; //取得刚才建立的file结构 if (filp->f_path.dentry == NULL) //断断是否初始化该文件 filp = __dentry_open(nd->path.dentry, nd->path.mnt, flags, filp, NULL); else path_put(&nd->path); return filp; } 继续跟踪__dentry_open()函数: static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt, int flags, struct file *f, int (*open)(struct inode *, struct file *)) { struct inode *inode; int error; f->f_flags = flags; f->f_mode = ((flags+1) & O_ACCMODE) | FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE; //文件模式 inode = dentry->d_inode; //获取该文件的索引节点inode if (f->f_mode & FMODE_WRITE) { //如果是可写模式 需要判断文件所在的文件系统可写? error = __get_file_write_access(inode, mnt); // 特殊文件不需要判断哦 if (error) goto cleanup_file; if (!special_file(inode->i_mode)) file_take_write(f); } f->f_mapping = inode->i_mapping; f->f_path.dentry = dentry; //初始化f_path指向的dentry f->f_path.mnt = mnt; //初始化f_path指向的mnt f->f_pos = 0; //读写位置指针 f->f_op = fops_get(inode->i_fop); //在inode中获取文件的操作方法 file_move(f, &inode->i_sb->s_files); error = security_dentry_open(f); if (error) goto cleanup_all; if (!open && f->f_op) open = f->f_op->open; if (open) { error = open(inode, f); //就是在这里执行文件open操作了 if (error) goto cleanup_all; } f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC); file_ra_state_init(&f->f_ra, f->f_mapping->host->i_mapping); /* NB: we're sure to have correct a_ops only after f_op->open */ if (f->f_flags & O_DIRECT) { if (!f->f_mapping->a_ops || ((!f->f_mapping->a_ops->direct_IO) && (!f->f_mapping->a_ops->get_xip_mem))) { fput(f); f = ERR_PTR(-EINVAL); } } return f; …………….. } 将inode的信息初始化给file后,调用f->f_op->open 执行文件的open操作 ,我们就拿字符设备为例子:如果在注册字符设备时定义了文件的open函数的话,这个时候就会调用定义的open函数了. 到这里文件就已经打开了. 不过我们还需要把这个file struct保存在当前进程中,以便可以由fd在这个进程中找到这个file,然后才能执行文件的其它操作哦~~~~~~~~~~. 那么这个file是咋个安装在当前进程中的呢?这时我们就需要回到函数do_sys_open()中看看了, 其中有一个函数fd_install();它的作用就是将file安装在当前进程对应的文件描述表中. 代码如下: void fd_install(unsigned int fd, struct file *file) { struct files_struct *files = current->files; struct fdtable *fdt; spin_lock(&files->file_lock); fdt = files_fdtable(files); BUG_ON(fdt->fd[fd] != NULL); rcu_assign_pointer(fdt->fd[fd], file); spin_unlock(&files->file_lock); } 可见上面的代码就是取出当前进程的文件描述表fstable,然后根据文件描述符fd的值把file对象安装在对应的当前进程的文件描述表中. 欧拉,VFS的Open工作就完了哦. 三: 小结 本文讨论了VFS如何去打开一个文件,这些都只是第一步; 在后面的文章将继续分析VFS读写文件是如何实现的. 当然了解完open的流程后, VFS的read,write操作的分析就会是水到渠成了.
转载请注明原文地址: https://www.6miu.com/read-47448.html

最新回复(0)