linux内核分析之文件系统
- linux内核分析之文件系统
- 一文件系统的安装和卸载分析
- 1 文件系统的安装
- 11 总领提纲
- 12 代码分析
- 13 实例考察path_walk
- 2 文件系统的卸载
- 21 提纲
- 22 代码分析
- 1 文件系统的安装
- 二问答归纳
- 1 问题
- 2 回答
- 三文件的打开
- 1打开文件的本质
- 2打开文件的过程
- 四文件创建
- 1 文件创建的过程
- 2 关于文件创建的理解
- 一文件系统的安装和卸载分析
一、文件系统的安装和卸载分析
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函数完成。
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
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结构
函数调用链为:
四、文件创建
在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号对应起来并且写入父目录的数据块中
- 调用父目录的dentry->inode->i_op->create创建一个文件。
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,则认为该块组放不下一个目录,这时候需要寻找另一个块组进行创建。