linux内核分析之文件系统

linux内核分析之文件系统

一、文件系统的安装和卸载分析

1.1 文件系统的安装

1.1.1 总领提纲

文件系统的安装过程中,有几个重要的数据结构:

  • file_system_type : 这个数据结构是VFS进入具体文件系统的一个转折点,因为该文件系统中有一个函数指针read_super,这个函指针用于将设备上的super_block读入内存,并且建立起VFS的super_block。该结构只是用于在安装时说明”如何安装该文件系统”的,并不作为被安装到安装节点的内容部分。
  • vfsmount : 这个数据结构记录了目录节点上安装的文件系统详细信息。
  • super_block : 这个数据结构可以说是一个文件系统对象实例,一旦某个目录节点与之关联起来,就可以说这个目录节点上挂载了该文件系统。

另外,在安装文件系统之前,从设备文件层面看,一个块设备是可以被访问的。只不过这时候访问时只能当作一个特殊文件来读写,也就是说只是一个大的线性空间,或者说这时候的块设备只是一个容量极大的文件而已。但是安装了文件系统之后,整个文件系统作为块设备的代理,通过目录节点访问,这时候对块设备的管理就可以按照一定格式来管理,读写也是按照一定格式来读写,在文件系统的管理之下,块设备的灵活性极大。

1.1.2 代码分析

文件系统的安装工作主要由do_mount函数完成。
[fs/super.c : 1338~1445 : do_mount]

1338 long do_mount(char * dev_name, char * dir_name, char *type_page,
1339                   unsigned long flags, void *data_page)
1340 {
1341         struct file_system_type * fstype;
1342         struct nameidata nd;
1343         struct vfsmount *mnt = NULL;
1344         struct super_block *sb;
1345         int retval = 0;
1346         
             /* .......此处省略代码若干  */
1385         /* ... filesystem driver... */
1386         fstype = get_fs_type(type_page);
1387         if (!fstype)
1388                 return -ENODEV;
1389 
1390         /* ... and mountpoint. Do the lookup first to force automounting. */
1391         if (path_init(dir_name,
1392                       LOOKUP_FOLLOW|LOOKUP_POSITIVE|LOOKUP_DIRECTORY, &nd))
1393                 retval = path_walk(dir_name, &nd);
1394         if (retval)
1395                 goto fs_out;
1396 
1397         /* get superblock, locks mount_sem on success */
1398         if (fstype->fs_flags & FS_NOMOUNT)
1399                 sb = ERR_PTR(-EINVAL);
1400         else if (fstype->fs_flags & FS_REQUIRES_DEV)
1401                 sb = get_sb_bdev(fstype, dev_name, flags, data_page);
1402         else if (fstype->fs_flags & FS_SINGLE)
1403                 sb = get_sb_single(fstype, flags, data_page);
1404         else
1405                 sb = get_sb_nodev(fstype, flags, data_page);
1406 
             /*......此处省略代码若干*/
1414 
1415         /* Refuse the same filesystem on the same mount point */
1416         retval = -EBUSY;
1417         if (nd.mnt && nd.mnt->mnt_sb == sb
1418                    && nd.mnt->mnt_root == nd.dentry)
1419                 goto fail;
1420 
1421         retval = -ENOENT;
1422         if (!nd.dentry->d_inode)
1423                 goto fail;
1424         down(&nd.dentry->d_inode->i_zombie);
1425         if (!IS_DEADDIR(nd.dentry->d_inode)) {
1426                 retval = -ENOMEM;
1427                 mnt = add_vfsmnt(&nd, sb->s_root, dev_name);
1428         }
1429         up(&nd.dentry->d_inode->i_zombie);
1430         if (!mnt)
1431                 goto fail;
1432         retval = 0;
             /*......此处省略代码若干*/
1445 }

第一步:查找file_system_type
1386: 系统支持的每种文件系统都有一个file_system_type结构用于描述和记录文件系统的一些特性。file_system_type定义于[include/linux/fs.h : 839~846],该结构在系统启动,或者是文件系统作为模块被加载进入内核时注册到一全局链表上。这里通过遍历该链表并且比对字符串从而查找到文件系统对应的file_system_type结构。

第二步:查找安装点的dentry结构。
1391~1393: 查找安装点的dentry结构,返回的nd.dentry即为根据路径名查找到的安装节点dentry。而nd.mnt是目前安装节点所在的文件系统的安装信息。

第三步:将待安装文件系统的super_block读取进内存并且建立起VFS的super_block。
1401 : 考虑非特殊文件系统的情况,调用get_sb_bdev读取超级块。该函数定义于[fs/super.c : 785~847 : get_sb_bdev]

第四步:开始安装,连接dentry—>vfsmount—>super_block
调用add_vfsmnt函数进行文件系统的安装,实际上是利用vfsmount结构将安装节点和super_block关联起来。调用完后dentry->d_vfsmnt链表上挂接有vfsmount,vfsmount->mnt_mountpoint = dentry,vfsmount->mnt_root = super_block->s_root。dentry、vfsmount、super_block如图所示:

(链接后的dentry、vfsmount、super_block之间的关系,详情请参考这篇文章)

1.1.3 实例考察path_walk

当一个目录节点/home/User/dir被挂接了一个设备时,当利用路径查找/dir下文件时会是如何的呢?path_walk相关部分如下。
[fs/namei.c : 501~511 : path_walk]

 501                 dentry = cached_lookup(nd->dentry, &this, LOOKUP_CONTINUE);
 502                 if (!dentry) {
 503                         dentry = real_lookup(nd->dentry, &this, LOOKUP_CONTINUE);
 504                         err = PTR_ERR(dentry);
 505                         if (IS_ERR(dentry))
 506                                 break;
 507                 }
 508                 /* Check mountpoints.. */
 509                 /*如果该节点是一个挂载节点,则前进到挂载设备的根目录中去*/
 510                 while (d_mountpoint(dentry) && __follow_down(&nd->mnt, &dentry))
 511                         ;

510 : 调用d_mountpoint检查该节点是不是挂载节点,如果是挂载节点就进入被挂载设备的根节点中,用挂载设备的根节点替代这次查找得到的dentry。逻辑上与该语句等效dentry = nd->mnt->mnt_root。d_mountpoint和__follow_down分别定义于[include/linux/dcache.h : 261][fs/namei.c : 352]

[include/linux/dcache.h : 261]
261 static __inline__ int d_mountpoint(struct dentry *dentry)
262 { 
263         return !list_empty(&dentry->d_vfsmnt);
264 }

[fs/namei.c : 352]
 352 static inline int __follow_down(struct vfsmount **mnt, struct dentry **dentry)
 353 { 
 354         struct list_head *p;
 355         spin_lock(&dcache_lock);
 356         p = (*dentry)->d_vfsmnt.next;
 357         while (p != &(*dentry)->d_vfsmnt) {
 358                 struct vfsmount *tmp;
 359                 tmp = list_entry(p, struct vfsmount, mnt_clash);
 360                 if (tmp->mnt_parent == *mnt) {
 361                         *mnt = mntget(tmp);
 362                         spin_unlock(&dcache_lock);
 363                         mntput(tmp->mnt_parent);
 364                         /* tmp holds the mountpoint, so... */
 365                         dput(*dentry);
 366                         *dentry = dget(tmp->mnt_root);
 367                         return 1;
 368                 }
 369                 p = p->next;
 370         }
 371         spin_unlock(&dcache_lock);
 372         return 0;
 373 }

1.2 文件系统的卸载

1.2.1 提纲

文件系统的卸载主要是拆除dentry、vfsmount、super_block之间的链接关系。在文件系统卸载时,完成了以下工作:

  • 将vfsmount从相关的list中移除
  • 将所有文件系统的inode、super_block、数据块会写到设备(sync)
  • 释放所有该文件系统的dentry

1.2.2 代码分析

文件系统的卸载,主要由do_umount函数完成。
[fs/super.c : 1045]

1045 static int do_umount(struct vfsmount *mnt, int umount_root, int flags)
1046 {
1047 /* 入口参数:
1048  *      vfsmount *mnt : 卸载点上的安装信息结构,在安装文件系统时,该结构被填充。
1049  *      umount_root : 为0时表示当前要卸载的不是根目录,为非0时,表示当前要卸载根目录
1050  *
1051  * */
1052 
1053         struct super_block * sb = mnt->mnt_sb;
             /*......此处省略代码若干*/
1065         //如果当前要卸载的mnt挂载点是根目录,则进行只读重装
1066         if (mnt == current->fs->rootmnt && !umount_root) {
1067                 int retval = 0;
1068                 /*
1069                  * Special case for "unmounting" root ...
1070                  * we just try to remount it readonly.
1071                  */
1072                 mntput(mnt);
1073                 if (!(sb->s_flags & MS_RDONLY))
1074                         retval = do_remount_sb(sb, MS_RDONLY, 0);
1075                 return retval;
1076         }
1077 
1078         spin_lock(&dcache_lock);
1079 
1080         /*
1081          * 如果该文件系统被安装多次,并且引用技术大于2 ,说明还有其它的目录
1082          * 节点挂载了该文件系统,这时只需要移除vfsmount即可,不做回收工作
1083          * */
1084         if (mnt->mnt_instances.next != mnt->mnt_instances.prev) {
1085                 if (atomic_read(&mnt->mnt_count) > 2) {
1086                         spin_unlock(&dcache_lock);
1087                         mntput(mnt);
1088                         return -EBUSY;
1089                 }
1090                 if (sb->s_type->fs_flags & FS_SINGLE)
1091                         put_filesystem(sb->s_type);
1092                 /* We hold two references, so mntput() is safe */
1093                 mntput(mnt);
1094                 /*移除vfsmount*/
1095                 remove_vfsmnt(mnt);
1096                 return 0;
1097         }
             /*......此处省略代码若干*/
1130 
1131         /*
1132          * 注:当一个dentry在内存中建立起来之后,每当被使用一次,
1133          *     则其引用计数加1,被用完之后引用计数减1直到
1134          *     一个dentry引用计数变为0之后,说明该当前已经
1135          *     没有进程使用该dentry了,但根据程序的局部性
1136          *     原理,该dentry不会被马上释放掉,而是被链入
1137          *     由LRU管理的unused_list队列当中,因为它很可能又会
1138          *     被再次使用,它会一直在该队列中直到被再次使用或者
1139          *     被LRU回收。当文件系统被卸载时,所有属于该
1140          *     文件系统的dentry都会被立即回收,而不会等到被LRU回收。
1141          * */
1142         //释放所有ununed_list中的dentry结构。
1143         shrink_dcache_sb(sb);
1144         //立即将内容回写到设备
1145         fsync_dev(sb->s_dev);
1146         if (sb->s_root->d_inode->i_state) {
1147                 mntput(mnt);
1148                 return -EBUSY;
1149         }
            /*......此处成略代码若干*/
1162         remove_vfsmnt(mnt);
1163 
1164         kill_super(sb, umount_root);
1165         return 0;
1166 }

第一步: 将vfsmount从相关的list中移除
[fs/super.c : 411~428 : remove_vfsmnt]

 411 static void remove_vfsmnt(struct vfsmount *mnt)
 412 {
 413         /* First of all, remove it from all lists */
 414         list_del(&mnt->mnt_instances);
 415         list_del(&mnt->mnt_clash);
 416         list_del(&mnt->mnt_list);
 417         list_del(&mnt->mnt_child);
 418         spin_unlock(&dcache_lock);
             /*.....此处省略代码若干*/
 428 }             

414:将vsfmount从该文件系统super_block->s_mounts维护的vsfmount链表中删除。
415:将vsfmount从该安装节点dentry->d_vfsmnt维护的vsfmount链表中删除。
416:将vsfmount从其父设备,即上一层目录的文件系统维护的mnt_child链表中删除
417:将vsfmount从内核维护的全局mnt_list链表中删除。

第二步: 释放所有该文件系统的dentry
[fs/super.c : 1103]

1103         shrink_dcache_sb(sb);

第三步: 将该文件系统的super_block以及所有的inode会写到设备内。

1105         fsync_dev(sb->s_dev);

二、问答归纳

2.1 问题:

  • (1) 文件系统安装意味这什么?
  • (2) 文件系统的安装完成了哪些工作?
  • (3) 文件系统的卸载完成了哪些工作
  • (4) 在某个目录节点安装了一个文件系统后,super_block和vfsmount与dentry关联起来的作用是什么?

2.2 回答:

  • (1) 意味着安装节点dentry与一个vfsmount关联起来了,并且vfsmount与一个具体文件系统的super_block关联起来。
  • (2) 安装完成的工作有:
    • 获取file_system_type
    • 获取安装节点的dentry
    • 获取被安装文件系统的super_block*(通过file_system_type->super_block读入)*
    • 关联dentry、vfsmount、super_block
  • (3) 卸载完成的工作有:
    • 将vfsmount从相关的list中移除
    • 将所有文件系统的inode、super_block、数据块会写到设备(sync)
    • 释放所有该文件系统的dentry
  • (4) 一旦某个目录节点的dentry与一个文件系统的vfsmount关联起来之后,以后访问该节点时,就会检测到其与vfsmount相关联,检测到该节点与一个vfsmount已经关联之后,就会自动转入vfsmount->mnt_root内进行访问,而不是访问原dentry的内容。

三、文件的打开

3.1打开文件的本质

文件的打开,本质上是建立起进程与文件之间的链接,也就是file结构,并且返回file结构的索引fd。使得进程能够通过file结构来对文件进行访问。

3.2打开文件的过程

  • (1) 从进程的task_struct中分配一个未使用的fd
  • (2) 通过路径名查找或者创建一个dentry
    • 通过path_init、path_walk查找路径名对应的dentry
    • 如果查找不到该路径名的dentry,并且设置了CREATE标志,则在路径名最后一个目录下创建该文件。
  • (3) 打开dentry,返回一个建立号的file结构
    • 分配一个空闲的file结构
    • 填充file中的VFS层信息,在这里复制了dentry->inode->i_fop
    • 调用inode->i_fop打开设备
    • 返回file结构

函数调用链为:
[fs/open.c : 746 : sys_open]:

四、文件创建

在1.2的分析中,我们知道,在open_namei函数中,一旦查找路径名对应的dentry不存在的时候,就要在路径名最后一个目录下创建该文件。现在来分析文件创建的过程。

4.1 文件创建的过程

  • (1) 调用lookup_hash在父目录下搜索dentry,考虑到这里是创建文件的情况这里会搜索不到,所以在内存中分配一个dentry
  • (2) 调用vfs_create创建文件
    • 调用父目录的dentry->inode->i_op->create创建一个文件。
      • dentry->inode->i_op->create创建一个文件
      • 调用ext2_new_inode创建inode
        • 调用 new_inode在内存中分配一个inode结构
        • 根据位图在设备上分配一个inode(实际上这里根据位图分配了一个inode节点号)
        • 填充部分inode信息,包括mode字段,fsuid等等
      • 填充VFS层inode信息,包括inode->i_op、inode->f_op
      • 调用ext2_add_entry将dentry和inode号对应起来并且写入父目录的数据块中

4.2 关于文件创建的理解

  • 在目录parent下创建一个文件,这是因具体文件系统而异的,所以create具体由parent->inode->i_op->create来执行。在parent下创建文件,主要作的工作是分配inode号,然后使用这个inode号和文件名构成一个ext2_dir_entry_2结构写入parent的数据块中(由parent对应的inode号索引得到)。请注意,dentry和inode是VFS层的对象,它们仅存在于内存中,设备上存放的是ext2_dir_entry_2和ext2_inode。对于具体文件系统来说,访问和管理文件只需要inode,而dentry只是用于支持VFS层的。

附注:分配inode的原则

  • 为了提高磁盘访问速度,所以将磁盘上同一盘面的块(扇区)被划分为一个块组来管理。并且另一方面,在创建一个文件时,它的inode和数据块应当也被分配到同一个块组内,因为inode和数据块的访问总是形影不离的。因此,,设备上的块组中存放的它的inode和数据块应当也被分配到同一个块组内,因为inode和数据块的访问总是形影不离的。因此,,设备上的块组中存放的ininode数量和数据块数量是成一定比例的。这个比例由文件平均大小得到的。比如说,系统上文件平均大小为fsizes,一个块组的总大小为gbsizes,一个数据块的大小为bsizes,则该块组能放下的文件数量为nf = gbsizes / fsizes。每个文件需要的数据块为nb = fsizes / bsizes。这样,inode和数据块的比例就为 nf : nb = gbsizes / bsizes : fsizes / bsizes。这样格式化了之后,在创建一个文件时,一旦块组内有空闲的inode的时候,很大概率上空闲的数据块也足以存放该inode对应的文件数据。
  • 当创建一个目录的时候,也希望该目录下的文件(目录下的子目录也是文件,看作一个文件来处理)都能处于同一个块组上,这样才能提高磁盘访问效率。同样的,每个目录下的文件数量也有一个统计值(系统平均值),假设为N,创建目录时,一旦发现块组内的空闲节点小于N,则认为该块组放不下一个目录,这时候需要寻找另一个块组进行创建。

猜你喜欢

转载自blog.csdn.net/u013298300/article/details/51214216