linux fs 会为每个文件分配两个元数据结构:index inode & directory entry
文件的块可能会被缓存在内存,例如目录结构块、inode块、数据块等。目录是一个特殊的文件,有自己的inode。超级块始终会被缓存到内存中。
目录不等于目录项,前者是一种fs组织结构,后者是目录中的一个具体条目,包含文件的与其他目录项的关联信息
磁盘的最小读写单位为扇区,保证它写入的原子性;fs 将一个或多个扇区封装为块,由fs实现额外的逻辑保证块写入的原子性
假设一个扇区512B,一个块4KB,则一次可写入8个扇区
磁盘的每个分区一般被fs划分为三个部分:
fs需要把自己的根目录挂载到某个现有目录上才可以正常使用,例如把根目录挂载到 /mnt/usb
fs可分为三类:
我们希望为用户提供一个统一的fs接口,这被称为vfs
linux 中,打开的文件被抽象为一个描述符,即 fd。每个进程都有自己的文件描述符表,即打开的文件的表。每个表项包含以下信息:
用户以字节为单位读写数据,内核以块为单位读写。fs提供了二者之间的桥梁:
连续存储/非连续存储
inode/文件头需要记录其起始块以及长度,以找到整个文件
链表方式又分为隐式链表和显示链表
对于隐式链表,inode指向起始块和末尾块,每一个数据块留出一部分空间指向下一个数据块
对于显示链表,它将链接各个数据块的指针都存到一个表里,以-1标记结束,解决了隐式链表的两个问题
对于索引,它为每个文件创建一个指向文件数据块的索引块,可以通过索引块找到数据块
对于更大的文件,可以采用链式索引块,即在每个索引块的末尾指向下一个索引块
或者多级索引块,索引块里面可以指向索引块,类似于多级页表:
早期Unix FS使用了多级索引:0~n级索引,文件头/inode中存放 13 个指针:
有三种常见的方式:空闲表、空闲链表、位图
空闲表:每个表项=首块块号+个数,连续分配
空闲链表:每个空闲块指向下一个空闲块,主存中只需保存指向首个空闲块的指针
以上二者都不适用于大型的文件系统,因为建立的空闲表和空闲链表都可能太大。
通常使用位图表示大型文件系统的块使用情况:0空闲1已分配
上述的位图表示使用 1个位图块+一些列数据块 可表示128MB的空间,这远远不够。
linux 把磁盘划分为多个相等的部分,每个部分被称为一个块组,它由以下内容构成:
目录也是文件,也有自己inode,其数据块中存储的是目录项
原始的目录项由inode号+文件类型+文件名构成,但在这个目录下查找文件则效率不高。我们可以把他们改成一个哈希表
通常,文件查找需要反复与磁盘交互,效率极慢,所以一般会缓存当前目录到内存中以加速查找
又名 Hard Link & Symbolic Link
硬链接通过让目录项指向同一个inode实现,这意味着它不能跨文件系统。同时系统会维护一个inode的硬链接引用计数,只有删除所有硬链接才会删除该文件。所有硬链接是平等的
软链接重新创建了一个文件,它的内容是另一个文件的文件名,因此可以跨文件系统。若删除原文件,则软链接文件仍存在但找不到指向的文件
常见的I/O方式的特性有:是否缓冲、是否直接、是否阻塞&同步
缓冲可以加速文件访问,但会有数据不一致的风险,对于重要文件建议手动刷新缓冲区,以维护数据一致性
其他利用缓冲的例子,例如endl才输出,刷新缓冲区,这可以减少系统调用的次数
OS有页缓存,利用页缓存的文件 I/O 被称为非直接 I/O,否则为直接I/O
O_DIRECT
则使用直接I/O,默认使用非直接I/O页缓存被刷新到磁盘上的时机有:
sync
刷新对于非阻塞I/O,可能需要不断轮询以等待数据准备好,这显然是一种浪费时间的做法。
于是我们有了 事件驱动的I/O 多路复用技术,例如 select、poll、epoll,等数据准备好了再通知应用程序进行操作。当没有事件发生时,当前进程会被阻塞,有事件时,会唤醒这个进程
以上都是同步I/O,异步I/O无需等待数据准备、也无需等待数据拷贝,例如:aio_read
通过 mmap 以及 buffered I/O 将文件读取到内存空间实际上都是读取到 Page Cache 中。
块缓存会通过拷贝的方式把数据交给页缓存,这会带来一定的性能开销;且相同的数据在内存中会存在两份,浪费内存。通过直接 I/O 可以直接与块设备交互,避免这种开销,但很慢。
在 linux 2.4 版本后,块缓存和页缓存被近似融合到了一起:
前缀树/字典树可以快速剪枝,但树如果非常稀疏则会导致树高非常高,效率降低。基数树解决了这个问题,它也可以被称为压缩前缀树
基数树可以用于路由,即一个大的正则表达式:
使用在 page cache 中,每一个文件我们都用一个基数树来保存。我们可以根据偏移量在基数树上找到对应的页
用户请求读3KB的数据,则会把整个块(4KB)以及其周围的一些数据都读出来,额外申请几个page
两种写策略:
这两种写策略基于以下 syscall,要么由用户发起,要么由内核发起:
优势:
劣势:
可以看到内存中的页由:块缓存+页缓存+swap区缓存组成