Linux IO 学习笔记(一)——文件系统架构

LInux任督二脉IO课程笔记,微信公众号:Linuxer。

VFS的磁盘文件系统的联系

Linux里面一切皆文件,相当于C++中的抽象基类,VFS给进程提供了一个统一的视图,让开发者不用关心每种文件系统的差别。VFS为每个文件分配一个file结构体,里面都要有一个struct file_operations类型的成员f_op,这个成员在open()文件的时候被特定文件系统赋值,最终实际操作文件的都是通过具体设备或具体文件系统的f_op来完成的(写过字符驱动的都知道,写字符驱动就是实现它的f_op)。

对于块设备,可以通过直接访问分区,也可以通过访问文件的方式来访问块设备。前者即直接通过裸设备访问块设备实际就是通过fs/block_dev.c的文件里的操作def_blk_fops,后者则是通过ext3/ext4等文件系统的f_op。(后面的裸块设备的address_space的操作为def_blk_aops,也在fs/block_dev.c中定义。address_space的含义后面会讲到。)

所以file结构中的f_op是将各种类型的文件系统的操作hook到VFS里面的,使VFS中有一个统一的视图。

那么,文件的f_op是怎么去操作特定的设备的,设备上的文件和目录又是怎么组织的呢?

我们先说第二个问题:

磁盘文件系统的组织方式

将磁盘某个分区格式化成某个文件系统(例如使用mkfs.ext4)后,这个分区的layout长这样:
这里写图片描述

可以看到磁盘上除了文件数据(data),还有一些用于管理和描述文件属性的元数据(metadata)。

superblock:记录了文件系统全局的信息,如每个block大小、block总数、inode大小、inode总数、free的inode和block数等顶层信息。

inode:磁盘上每个文件都有一个inode与之对应,inode是文件的唯一标识。磁盘中superblock后面会维护一个inode bitmap,描述哪些inode被占用了,inode位图之后还有一个block bitmap,描述磁盘中每块区域是否已经被使用了(例如inode的数据,元数据本身也会占block)。也就是说,inode位图和block位图会限制文件系统可创建文件的最大数量。

文件系统的空间被分割成一个一个的block,block位图就记录了磁盘上哪些block已经被使用了,哪些是空闲的。inode的数据部分也是以block为单位来管理的。一个inode的数据可能占用多个data blocks,因此创建/删除/修改文件时,除了相应block的内容和inode元数据的改变,block位图也要相应的更新。

block的位图后面还会有一个inode table,即inode表,前面inode位图只记录了那些inode号被占用了,inode表则记录已被使用的inode的更详细的信息,例如size、日期、属性、占用了哪些block等信息。这些信息在内核中通过不同的结构来记录。

inode diagram:一个inode的元数据里除了记录文件信息(size、创建日期、属性等),还要记录inode的数据位于磁盘中的哪些数据块(block),这就是inode diagram。inode diagram是包含在inode table里的,每个inode信息里都维护了自己的inode diagram。

这个diagram可能有多级,因为由于文件可以很大,可能占了很多数据块,所以一级inode diagram可能不足以描述足够多的磁盘块,可以通过间接指向的方法来支持大文件,因此一个inode占用的块可能包括direct blocks, indirect blocks和double indirect blocks。如果一个inode占的块太多,也不好记录和管理inode diagram,因此ext4增加了”extents”的概念,一个extent就是几个地址连续的blocks的集合。比如一个100MB的文件可能被分配给一个单独的extent,而不用像ext3那样新增25600个数据块的记录(假设一个数据块大小是4KB)。极大减少了inode diagram占用的空间,也可以支持更大的文件大小。

再后面就是存放文件数据的data blocks了,一个block通常是4KB(每个block的大小是在文件系统格式化的时候指定的)。当然前面的superblock、inode表等管理信息也占了block。

这些结构都是在存在于磁盘上的,kernel中在挂载了这个分区之后,会去读取磁盘中的superblock、bimap等信息,相应的在内核中也建立相应的数据结构来缓存这些信息。在kernel访问一个文件时,就会从磁盘中找到这个文件的inode信息,并利用inode信息在内核中构建一个struct inode结构体。在读写文件的时候就会去读取inode的data block并在内核中用struct bio结构来缓存这些blocks,并建立与page的联系(因此内核是按page读写的而不是按block)。在发生数据或元数据读写后,某个时间会将内存中的缓存内容和磁盘的内容做同步。

内核的inode结构里面有两个成员函数集i_opi_fop
file_operations: 由于inode对应了文件系统中的文件,因此必须记录特定文件系统的文件对应的file_operations。
inode_operations: 包含对inode本身的操作集,例如新建一个新的inode,根据文件名找到inode,以及mkdir/link/unlink/stat等操作。

内核里的super_block结构还包含了文件系统类型以及如何创建和销毁inode的方法(super_operations)等内容。注意你在挂载一个文件系统时,首先得保证你的内核支持这个文件系统才能挂载,要不然就没法赋值file_operations、inode_operations、super_operations这些操作集了。

dentry:对应的是一个路径,它是目录和文件的缓存,用于加快文件系统寻找inode。注意,磁盘中是不存在dentry的。
当open一个文件时,在其路径查询过程中为每一级路径创建一个dentry,例如打开/home/user/a.txt就会产生4个dentry,名字分别是”/”, “home”, “user”, “a.txt”。dentry就是文件路径的缓存,可以节省查找文件的时间。那么删除(unlink、rm)一个文件的时候,它在内核里的dentry也就没用了,因此也会被删除。

Linux里面的目录、子目录、文件都有inode与之对应,目录是一个特殊的文件,普通文件的内容(即inode数据)就是文件数据,而目录文件的内容就是这个目录下的文件或子目录的名字和inode号的列表

struct file结构:内核中的这个结构体代表了硬盘里一个文件的引用,我们知道一个硬盘真实文件对应一个inode,内核打开一次文件就产生一个file实例,如果多个地方都打开了这个文件,就有多个file实例
注意一点,文件名不是放在inode表里的,唯一描述一个文件只有inode号(上面说了,一个文件的文件名是放在其父目录的inode数据里面的)。

那么,在打开/usr/bin/emacs这个文件时,就涉及到三个目录文件,”/”, “/usr/”和”/usr/bin/”。我们知道一个inode的inode diagram里记录了它的数据在磁盘中存放的位置,而对于一个目录来说,它的inode的数据就是它下面的文件(或子目录)的列表。因此这里面就要从磁盘读取和查找三次目录文件inode的inode表和数据块:通过”/”的inode diagram找到找到其数据块在哪个block,在数据块的内容中找到其下面的”usr/”的inode号,……,最终找到emacs对应的inode号,就可以找到这个inode号对应的inode信息,然后从磁盘读出来。
这里写图片描述
在这过程中查找inode的时候,如果一个文件名已经有dentry缓存了,查找路径过程就会快很多,dentry中存放了文件名及其对应的inode,以及父目录的dentry和子目录的dentry链表等信息。

我们看到的文件的大小,不计算元数据的,元数据是文件系统本身实现的开销,所以新创建的文件大小是0。

符号链接和硬链接

上面说文件名和目录名,它们都是一个特殊inode(父目录的inode)数据里的内容,来指明文件名和目录对应的inode号。
那么硬链接,比如在目录a下有b文件,则给b创建其硬链接c,就是在a的inode的内容里添加一个条目c,并c指向和b相同的inode号,即,硬链接的创建和删除实际上操作的是其父目录文件,并没有创建新的inode。
符号链接(软链接)则是创建了一个新的inode,即符号链接在硬盘里是真实存在的,比如给a创建符号链接b,则a和b是两个不同的inode,只是b的inode里的内容指明了它是指向a的。

创建硬链接的命令:

ln a.out b.out  //给a.out创建一个硬链接b.out

创建符号链接的命令:

ln -s a.out b.out  //给a.out创建一个符号链接b.out

通过 ls -l -i 可打印出文件的inode号,就可以看出硬链接两个文件的inode号相同,创建日期也相同,而符号链接是不同的inode号,创建日期不同。

[root@ubuntu]test:$ mkdir a; cd a 
[root@ubuntu]test:$ touch 1 2 3
[root@ubuntu]test:$ cd ..
[root@ubuntu]test:$ ln -s a b
[root@ubuntu]test:$ ln b c
[root@ubuntu]test:$ ls -li
total 4
2633550 drwxrwxr-x 2 root root 4096  420 23:32 a
2633554 lrwxrwxrwx 2 root root    1  420 23:33 b -> a
2633554 lrwxrwxrwx 2 root root    1  420 23:33 c -> a
[root@ubuntu]test:$

对于硬链接需要注意:
1.硬链接不能跨本地文件系统,这很好理解,因为它们相同的inode呀。
2.也不能对目录建立硬链接,因为场景可能就复杂化了,例如对..做硬链接就可能乱了,又例如非空目录创建硬链接,你还要给目录和子目录都改变inode内容,所以理论上不是不能,而是避免复杂。

对于符号链接需要注意:
1.针对目录的软链接,即使原目录非空,用 “rm -rf” 是删除不了原目录里的内容的,你删的只是这个软链接。
2.针对目录的软链接,

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值