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

:概述

  文件的打开读写操作是一项复杂的工作,本文只讨论VFS层系统调用打开文件的实现,文件的打开操作流程大致是这样的:首先在当前进程的文件描述表fdtale中分配一个空的文件描述符fd ,然后在filp_cachep中创建一个file struct ,调用do_path_lookup()找的文件的inode ,取出inode的文件操作方法file_operations赋给file struct ,并调用f->f_op->open()执行打开的操作.最后根据根据文件描述符fdfile安装在当前进程文件描述表fdtable对应的位置.欧了,整个打开文件的流程大致就是这个样子的了.这样在以后读写文件操作时,只需要根据文件描述符fd,就可以在当前进程的描述表中找到该文件的file对象,然后调用f->f_op操作方法对文件进行操作.

可以这么大略的理解(自我理解):还是这个图

根据进程需要的file,建立一个file_struct结构,从内存中形成的VFS目录树中查找到所要操作的这个文件,找到其inodedentry,然后将inode中的文件操作指针取出,放到file_struct中的f_op中,以后的操作就可以直接从这个地方操作。

 

: open的实现流程

先来看看我画的一个VFS文件操作的流程图吧,有了这个图再来看下面的代码就要清晰的多了哦~~~~~~~ ..

从上面的图中我们可以看到,其实 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对象安装在对应的当前进程的文件描述表中欧拉,VFSOpen工作就完了哦.

 

小结

  本文讨论了VFS如何去打开一个文件,这些都只是第一步在后面的文章将继续分析VFS读写文件是如何实现的当然了解完open的流程后, VFSread,write操作的分析就会是水到渠成了

转载自http://blog.chinaunix.net/u3/108768/showart_2188238.html

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭