Linux VFS文件系统之创建文件

----------------------------

#纯属个人理解,如有问题敬请谅解!

#kernel version: 2.6.26

#Author: andy wang

----------------------------

一: 概述

   在上文中分析了VFS根目录是如何创建的;既然有了根VFS这棵树就能开枝散叶,在内存中慢慢发展壮大;本文将介绍如何在VFS中建立一个目录文件和普通文件.

先回顾一下上文, 在rootfs创建根目录索引节点时有这么一段代码:

case S_IFDIR: //目录文件

inode->i_op = &ramfs_dir_inode_operations; //索引节点的操作方法

inode->i_fop = &simple_dir_operations;  //目录文件的操作方法

“/”是一个目录文件,所以在该目录下创建文件就会调用inode->i_op中定义的方法. 所以本文主要研究的就是linix VFS 是如何找到目录文件对应的索引节点 ,然后调用索引节点方法实现文件的创建.

先总体感观一下, 如: 我现在要在 “/home/andy/test/”目录下创建一个hello.c的文件,既 /home/andy/test/hello.c 显然这是一个绝对路径,那么现在就必须从根目录”/”开始一次往下查找, 最后在内存中找到”test”的目录项对象, 我们的目的是在test目录下建立hello.c文件; 所以就需要找到test dentry关联的inode,然后调用inode->i_op创建文件的方法,在内存或者在具体存储介质中建立这个文件.

需要注意的是为了加快查找速度, inode和dentry都是存在 cache中的,而cache的空间是有限的, 所以当文件系统挂载后内存中只有根目录的inode和dentry, 而其于文件的inode和dentry都是动态建立起来的,如果一个目录对应的inode和dentry都在内存中,那么它的父亲对应的inode和dentry也一定在内存中. 了解这点后再看后面的代码就要好理解一点了。
 
二: 路径查找

  从上面的分析可以看出在建立一个文件时,VFS需要做的一个重要的事情就是进行路径查找, 找到目的文件父目录的dentry和inode,最后调用inode->i_op.所以得先从路径查找入手了.

查找路径的函数为do_path_lookup(),这个函数比较烦琐,下面是基本调用流程图:

根据这个主线分析一下路径查找的过程;

首先看看do_path_lookup()代码的片段:

static int 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;  //取得当前进程的fs
 

         nd->last_type = LAST_ROOT; /* if there are only slashes... */

         nd->flags = flags; 

           //这个flag有如下几个宏定义:

           //LOOKUP_FOLLOW               路径最后一个分量是符号链接,则追踪它 

           //LOOKUP_DIRECTORY   路径最后一个分量为目录

           //LOOKUP_CONTINUE       路径中还有文件名要检查

           //LOOKUP_PARENT      查找路径中最后一个分量所在的目录

 

         nd->depth = 0;

         if (*name=='/') {        //查找需要从当前根目录开始(绝对路径)

                   read_lock(&fs->lock);

                   if (fs->altroot.dentry && !(nd->flags & LOOKUP_NOALT)) {

                            nd->path = fs->altroot;

                            path_get(&fs->altroot);

                            read_unlock(&fs->lock);

                            if (__emul_lookup_dentry(name,nd))

                                     goto out; /* found in altroot */

                            read_lock(&fs->lock);

                   }

                   nd->path = fs->root;  //取得根目录的dentry和vfsmnt

                   path_get(&fs->root);

                   read_unlock(&fs->lock);

         } else if (dfd == AT_FDCWD) {  //查找需要从当前工作目录开始(相对路径)

                   read_lock(&fs->lock);

                   nd->path = fs->pwd;

                   path_get(&fs->pwd);

                   read_unlock(&fs->lock);

         } else {

                   struct dentry *dentry;

 

                   file = fget_light(dfd, &fput_needed);

                   retval = -EBADF;

                   if (!file)

                            goto out_fail;

 

                   dentry = file->f_path.dentry;  //取得当前目录的dentry

 

                   retval = -ENOTDIR;

                   if (!S_ISDIR(dentry->d_inode->i_mode))  //判断文件是否为目录

                            goto fput_fail;

 

                   retval = file_permission(file, MAY_EXEC); //文件权限

                   if (retval)

                            goto fput_fail;

 

                   nd->path = file->f_path;   //在经过上面判断后成功取得进程当前目录的dentry和mnt

                   path_get(&file->f_path);

 

                   fput_light(file, fput_needed);

         }

        retval = path_walk(name, nd); //此时路径第一个分量的dentry和mnt 已经保存在了nd->path中,继续查找

        ………………….

}
 

在这里我们可以看出来nd其实就像是一个”路标” , 它指向了我们查找到路径的什么位置.

继续跟踪代码path_walk(),这个函数调用了函数link_path_walk(name, nd),最终调用函数__link_path_walk(),来看一下代码片段:

static int __link_path_walk(const char *name, struct nameidata *nd){

         struct path next;

         struct inode *inode;

         int err;

         unsigned int lookup_flags = nd->flags;  //这个flag很重要,决定后面查找方式         

         while (*name=='/') //如果路径第一个字符为”/”,则跳过

                   name++;

         if (!*name)

                   goto return_reval; 

         inode = nd->path.dentry->d_inode; //取得inode是为了后面判断该目录或文件的操作权限

         if (nd->depth)

                   lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE);

 
         /* 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); //文件权限判断

                   if (err == -EAGAIN)

                            err = vfs_permission(nd, MAY_EXEC);
                   if (err)
                            break;

                   this.name = name;
                   c = *(const unsigned char *)name;

                    hash = init_name_hash();

                   do {

                            name++;

                            hash = partial_name_hash(c, hash); //计算路径hash值

                            c = *(const unsigned char *)name;

                   } while (c && (c != '/'));

                   this.len = name - (const char *) this.name;

                   this.hash = end_name_hash(hash);  //到这里路径中的一个分量(目录名)的散列值就计算完了

                   //此时在this中保存了保存了目录名,目录名字长度和该目录散列值

                   if (!c)

                            goto last_component;

                   while (*++name == '/');

                   if (!*name)

                            goto last_with_slashes;

                   //上面的代码就已经解析出了路径名中的一个分量,并保存在this中

                   if (this.name[0] == '.') switch (this.len) { 如果名字第一个字符是”.” 

                            default:    //路径名出错

                                     break;

                            case 2:        

                                     if (this.name[1] != '.')  //表示上级目录

                                               break;

                                     follow_dotdot(nd);

                                     inode = nd->path.dentry->d_inode;

                                     /* fallthrough */

                            case 1:    //表示当前目录, 立刻继续查找下个路径分量

                                     continue;

                   }

                  …………………

                   /* This does the actual lookups.. */

                   err = do_lookup(nd, &this, &next); //在cache中查找该分量是否存在,如果不存在会建立一个dentry

                   if (err)

                            break;

 

                   err = -ENOENT;

                   inode = next.dentry->d_inode;

                   if (!inode)

                            goto out_dput;

                   err = -ENOTDIR; 

                   if (!inode->i_op)
                            goto out_dput; 

                   if (inode->i_op->follow_link) {

                            err = do_follow_link(&next, nd);

                            if (err)
                                     goto return_err;

                            err = -ENOENT;

                            inode = nd->path.dentry->d_inode;

                            if (!inode)

                                     break;

                            err = -ENOTDIR; 

                            if (!inode->i_op)

                                     break;

                   } else
                            path_to_nameidata(&next, nd);

                   err = -ENOTDIR; 

                   if (!inode->i_op->lookup)

                            break;

                   continue;

                   /* here ends the main loop */

 

                   …………………..

}

 可以看到这个函数实现很烦琐,简单的说这个函数就是依次解析路径中的每个分量,如果该目录项不在目录项高速缓存中那么就在目录项高速缓存中建立对于的dentry ,并将最后一个分量的父目录的dentry和mnt赋给路标nd,.

下面看一下怎么在cache中查找该目录项对象:

static int do_lookup(struct nameidata *nd, struct qstr *name,struct path *path){

         struct vfsmount *mnt = nd->path.mnt;

         struct dentry *dentry = __d_lookup(nd->path.dentry, name);//在父目录所在的散列表中查找该目录项对象

         if (!dentry)    

                   goto need_lookup;

         ………………  

        need_lookup: 

         dentry = real_lookup(nd->path.dentry, name, nd); //如果在__d_lookup中未找到,需要进一步查找

         if (IS_ERR(dentry))

                   goto fail;
         goto done;

         ……….

}

进一步看看real_lookup()的代码:

static struct dentry * real_lookup(struct dentry * parent, struct qstr * name, struct nameidata *nd)

{

         struct dentry * result;

         struct inode *dir = parent->d_inode;  //获取父目录对于的inode

         mutex_lock(&dir->i_mutex);

         result = d_lookup(parent, name);  //在这里又进行了一次查找,

         if (!result) {        //如果确认未找到,则需要在内存中建立一个目录项对象了

                   struct dentry * dentry = d_alloc(parent, name);  //分配一个dentry

                   result = ERR_PTR(-ENOMEM);

                   if (dentry) {

                            result = dir->i_op->lookup(dir, dentry, nd); //这是一个很中要的回调函数了,下面介绍

                            if (result)

                                     dput(dentry);

                            else

                                     result = dentry;

                   }

                   mutex_unlock(&dir->i_mutex);

                   return result;

         }

 
         ……………..

}

在这个函数中又一次调用了d_lookup()对dentry进行查找  ,这里为什么先后两次进行相同的查找操作呢?难道仅仅是为了使查找dentry更加准确?为啥子又要加这样的安全套呢?

好了,如果这次也没在目录项高速缓存中找到dentry,那么就需要在目录项高速缓存中新建一个dentry了.

这里需要关注一个回调函数dir->i_op->lookup(dir, dentry, nd); 这个函数在不同的文件系统实现是不同的, 在以后分析其他文件系统的时候再来感受, 这里只看一下rootfs中这个回调函数是咋个定义的:

其实在rootfs中创建inode时定义了inode操作方法(可参见上文),lookup实现函数为simple_lookup();

下面就来看看这个函数都干了些啥子:

struct dentry *simple_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
{

         static struct dentry_operations simple_dentry_operations = {

                   .d_delete = simple_delete_dentry,

         };

         if (dentry->d_name.len > NAME_MAX)   //linux下文件名字长度不得超过255

                   return ERR_PTR(-ENAMETOOLONG);

         dentry->d_op = &simple_dentry_operations;   //定义新建dentry操作方法

         d_add(dentry, NULL); 

         return NULL;

}

d_add(dentry, NULL)函数调用了d_instantiate(entry, inode)其实就关联dentry和inode了,不过因为这里的inode还没有创建所以就为空了, 另外还调用了一个函数d_rehash(entry);这个函数比较简单,就是计算出dentry对应的散列值,然后把它加到对应的散列表中 ,这样在查找的时候就可以快速的找到这个dentry了,而不会再去新建一个了.

至此路径查找的流程已经简单的跑了一遍了,根据查找的flags, 查找的结果放在了nd中,下面就可以用这个”路标”nd做具体的事情了。

三: 建立目录文件
 
看了上面的软件流程其实已经很清晰了, 我们先看看sys_mkdir()这个系统调用的代码片段:

asmlinkage long sys_mkdirat(int dfd, const char __user *pathname, int mode)

{

         int error = 0;

         char * tmp;

         struct dentry *dentry;

         struct nameidata nd;

         tmp = getname(pathname); //需拷贝 USER空间的pathname 到KERNEL空间

         ………

         error = do_path_lookup(dfd, tmp, LOOKUP_PARENT, &nd); //查找路径上面已经介绍

         if (error)
                   goto out;
                            
         dentry = lookup_create(&nd, 1); //创建目标文件的dentry (路径最后一个分量)

 
          …….
 

         error = mnt_want_write(nd.path.mnt);

         if (error)

                   goto out_dput;

         error = vfs_mkdir(nd.path.dentry->d_inode, dentry, mode); //创建目录文件的索引节点

         mnt_drop_write(nd.path.mnt);

         
         ………

}

在调用do_path_lookup()后,路径的最后一个分量(目标文件)的父目录信息已经保存到nd中了, 接下来要做的就是建立目标文件的dentyr了(也就是路径的最后一个分量的目录项对象) .目标文件的dentry是由lookup_create()函数创建的 ,实现方式和上面分析的是差不多的,这里就不列出代码了。

创建目录文件还需要创建目录文件对应的索引节点inode, 下面跟踪一下vfs_mkdir函数的实现:

int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)

{
         int error = may_create(dir, dentry, NULL);
         if (error)
                   return error;

         if (!dir->i_op || !dir->i_op->mkdir)  //检查父目录的索引节点操作方法

                   return -EPERM;
 
         mode &= (S_IRWXUGO|S_ISVTX);

         error = security_inode_mkdir(dir, dentry, mode);

         if (error)

                   return error;

         DQUOT_INIT(dir);

         error = dir->i_op->mkdir(dir, dentry, mode); //调用父目录文件索引节点操作方法创建inode

         if (!error)

                   fsnotify_mkdir(dir, dentry);

         return error;

}

这个函数比较简单,最后调用dir->i_op->mkdir(dir, dentry, mode)回调函数,

下面来看看rootfs inode操作方法是如何定义的:

static const struct inode_operations ramfs_dir_inode_operations = {

         .create                  = ramfs_create,     //创建普通文件

         .lookup                = simple_lookup,

         .link            = simple_link,

         .unlink                 = simple_unlink,

         .symlink     = ramfs_symlink,      //创建符号链接文件

         .mkdir                  = ramfs_mkdir,   //创建目录文件

         .rmdir                  = simple_rmdir,    

         .mknod                = ramfs_mknod,  //创建特殊文件

         .rename                = simple_rename,

};

可以看出rootfs 使用的inode操作方法和ramfs inode操作方法是一样的,因为它们都是内存文件系统 .

下面是ramfs_mkdir()代码片段:

static int ramfs_mkdir(struct inode * dir, struct dentry * dentry, int mode)
{
         int retval = ramfs_mknod(dir, dentry, mode | S_IFDIR, 0);

         if (!retval)

                   inc_nlink(dir);

         return retval;

}

ramfs_mknod()在内存中创建一个inode 模式为DIR,说明了我们创建的是目录文件。

ramfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)

{

         struct inode * inode = ramfs_get_inode(dir->i_sb, mode, dev); //在上篇文章rootfs挂载中已经介绍

         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);  //关联dentry和indoe

                   dget(dentry);       /* Extra count - pin the dentry in core */

                   error = 0;

                   dir->i_mtime = dir->i_ctime = CURRENT_TIME;  //设置创建时间

         }

         return error;

}

到此这个目录文件我们已经创建好了^^^^^^^^^^^.

 
四: 创建普通文件

     熟悉linux驱动的人都知道静态创建一个字符或者块设备是用mknod系统调用实现的.

那下面我们就来看看mknod是咋个创建一个普通文件的.

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);

         if (IS_ERR(tmp))

                   return PTR_ERR(tmp);

 

         error = do_path_lookup(dfd, tmp, LOOKUP_PARENT, &nd);

         if (error)

                   goto out;

         dentry = lookup_create(&nd, 0);

         if (IS_ERR(dentry)) {

                   error = PTR_ERR(dentry);

                   goto out_unlock;

         }

         if (!IS_POSIXACL(nd.path.dentry->d_inode))

                   mode &= ~current->fs->umask;

         error = may_mknod(mode);

         if (error)

                   goto out_dput;

         error = mnt_want_write(nd.path.mnt);

         if (error)

                   goto out_dput;

         switch (mode & S_IFMT) {

                   case 0: case S_IFREG:    //普通文件

                            error = vfs_create(nd.path.dentry->d_inode,dentry,mode,&nd);

                            break;

                   case S_IFCHR: case S_IFBLK:   //设备文件

                            error = vfs_mknod(nd.path.dentry->d_inode,dentry,mode,

                                               new_decode_dev(dev));

                            break;

                   case S_IFIFO: case S_IFSOCK:  //FIFO,SOCKET文件

                            error = vfs_mknod(nd.path.dentry->d_inode,dentry,mode,0);

                            break;

         }

         mnt_drop_write(nd.path.mnt);

 

         ……………….

}

从上面的代码可以看出此函数的实现和sys_mkdirat 其实都是差不多的,只是sys_mkdirat函数只能创建目录文件, 而sys_mknodat只能创建普通文件和特殊文件.

创建普通文件调用vfs_create(), 创建特殊文件调用vfs_mknod() ,其实他们的实现最终实现方式都是差不多的,,都需要调用ramfs_mknod() ,只是传入的文件类型参数(mode)不同而已.

 

五: 小结

     通过上面的分析,对目录文件和普通文件的创建有了一个大体的认识.,上面只是分析的是内存文件系统文件的创建, 实际的文件系统也就可以Mount到这些目录下了,但是实际的磁盘文件系统文件的创建要更复杂些了. VFS这棵树也就是这样慢慢发展起来的.


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
Linux VFS(Virtual File System)是Linux操作系统中的一个重要组件,用于处理文件系统的访问操作。VFS提供了一个抽象层,使用户和应用程序可以以一致的方式访问不同类型的文件系统,如ext4、NTFS等。VFS的开发是为了增强Linux系统的可移植性和灵活性,使其能够在不同的文件系统上运行,并提供统一的API接口。 在Linux VFS的开发中,主要涉及如下几个方面: 1. 文件系统注册和管理:VFS通过注册文件系统的方式,将具体的文件系统VFS绑定起来。开发者需要实现相应的文件系统操作函数,并注册到VFS中,以便VFS调用。同时,VFS还负责管理已注册的文件系统,并按照一定的规则进行调度和访问。 2. VFS数据结构设计:VFS中涉及到的数据结构设计非常重要,需要考虑到不同文件系统的特点和实现要求。常见的数据结构有inode、dentry、super_block等,它们之间的关系和作用需要合理设计和管理,以保证文件系统的正常运行。 3. 文件系统操作函数的实现:开发者需要根据具体的文件系统类型和需求,实现一系列文件系统操作函数,包括文件的创建、删除、读写等。这些函数需要按照VFS定义的接口规范来进行实现,以保证跨文件系统的通用性和可移植性。 4. 错误处理和异常情况处理:在VFS开发中,需要考虑到各种可能的错误和异常情况,如磁盘空间不足、文件不存在等。开发者需要合理处理这些异常情况,给用户提供友好的错误提示和处理方式,以保证系统的稳定性和可靠性。 总之,Linux VFS的开发是一个复杂而庞大的工作,需要对文件系统的原理和实现有深入的了解,同时具备良好的编程技巧和设计能力。通过开发和完善VFS,可以增强Linux系统的文件系统支持和扩展性,提高系统的性能和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值