2553 字
13 分钟
持久化
2023-10-04

为什么需要文件系统#

新鲜水果(内存中的数据)具有易失性,就像水果一样,如果不及时保存和处理,它们会很快腐烂或失去原有的新鲜度和营养价值。同样,内存中的数据在电源关闭或系统重启后就会丢失,无法再次访问或利用。

而果干(文件系统中的文件)是经过加工处理的水果,可以长期储存并保持其大部分的营养价值。即使时间过去很久,只要妥善保存,果干仍然可以食用,并提供所需的营养。文件系统持久化就像将新鲜水果制作成果干的过程,它确保数据即使在电源关闭或系统重启后也能保持不变,并且可以随时访问和利用。

因此,文件系统持久化的重要性在于它提供了一种机制,使得数据可以长期保存并随时可用,就像果干一样。这对于需要长期存储和处理大量数据的应用程序和系统来说至关重要,因为它们需要确保数据的安全性和可靠性,以便在需要时能够进行访问和分析

文件#

定义#

上层软件的抽象:一组有意义的记录的集合 操作系统对文件的抽象:视为字节序列

元信息#

atime文件中的数据库最后被访问的时间
mtime文件内容被修改的最后时间
ctime文件的元数据发生变化。比如权限,所有者等
类型
所属用户用户组
元信息

分类#

FCB和索引节点#

不采用索引节点之前所有文件的信息存放在目录项,目录内容比较庞大,查找文件的io次数多,采用索引节点后,把文件信息存放在索引节点中,目录项只用存放文件名和索引节点号,大大降低了io次数。使用索引节点方法计算io次数时不要忘了加上访问索引节点的一次io

内存索引节点对象#

是索引节点在内存中的缓存和副本,除了索引节点中的内容外还有索引节点号、状态(指示是否被修改过或者是否上锁)、引用计数、逻辑设备号、链接指针等字段

接口#

open create unlink read write seek close …

系统文件表 文件打开表#

文件保护#

权限类型#

ACL#

精简的访问控制列表#

rwxrwxrwx

  • 当目录没有x权限时候,无法进入该目录,进入该目录的子目录,列出该目录内文件,操作该目录内文件(读/写已存在文件),操作该目录内文件(增删)
  • 只有x权限时,能进入该目录,进入该目录的目录,打开目录里的文件,但是不能列出目录项,也不能增删目录里的文件

加密和口令#

文件共享#

  • 硬链接
  • 软链接

上层抽象(逻辑结构)#

  • 无结构文件
  • 结构化文件 由一组相似的记录组成,又称记录式文件。每条记录又若干个数据项组成 根据每条记录的长度是否相等,可以分为定长记录可变长记录两种

下层实现(物理结构)#

连续结构#

链接结构#

  • 隐式链接
  • 显式链接(FATfs)文件系统中存放一张FAT表, 在文件系统初始化时载入内存,每一个表项的内容是指向下一个盘块的指针,可以用一个特殊的数字1-1来表示没有下一项,计算文件的最大大小要注意这个无效状态,减去一个多余的块大小。另外FAT表还能把空闲盘块链接起来,来实现空闲盘块管理,FCB中存放的应该有第一个盘块与最后一个盘块的盘块号

索引结构#

  • 单级索引
  • 多级索引
  • 混合索引
  • unix system v 共13个指针:一组数据块指针共10个,一级索引块指针、二级索引块指针、三级索引块指针各1个

比较#

评价指标 动态性能(扩容缩容)开销 稳定性 随机和顺序访问性能 其他

访问第n个逻辑块(从1开始)的io次数动态性能(扩容)开销随机访问顺序访问其他支持的最大大小计算
连续存储1(目录项已被读入内存)最小最好最好(2^大小字段的位数-1)*盘块大小
隐式链接n(目录项已被读入内存)每一个数据块都需要存放一个指针最差一般文件数据块的任一个指针出问题会导致文件数据丢失(2^指针位数-1)*(盘块大小-指针字节数)
显式链接1(FAT表已被读入内存,只用遍历内存中的FAT表文件系统中需要开辟一块区域做FAT表较差(不用多次io,但要遍历内存中的FAT表)一般(2^指针位数-1)*盘块大小
索引方法数据块被MM级索引块管辖就用M+1M+1次,索引节点直辖的数据块仅用一次(inode节点已被读入内存)需要额外的索引块,维护索引结构好,但是要访问额外的数据块好,但是要访问额外的数据块所能管辖的数据块数量乘以盘块大小

目录#

目录结构#

  • 单级结构
  • per user
  • tree
  • dag

操作#

  • find
  • createFile、createDir
  • deleteFile、deleteDir
  • move
  • ls

查找实现#

  • 线性表
  • 索引
  • 哈希
  • 树查找

文件系统#

Linux-storage-stack-diagram_v6.9

  • 用户层
  • 文件系统层
    • vfs层 文件系统具体要实现的接口 superblock dcache file inode pagecache
    • 具体的文件系统实现
  • 通用块层 io队列 io调度器 对文件系统的io请求进行排队,再通过重新排序和请求合并,然后才发送给下一级的设备层
  • 设备层 驱动终端设备

典型的文件系统布局#

  • MBR与分区表
  • 引导块
  • 超级快
  • 空闲盘块管理区
  • inode盘区
  • 数据区(根目录与文件和目录区)

VFS#

vfs抽象#

  • 超级块 super_block 是文件系统的元信息,代表一个文件系统
  • 索引节点对象 inode 磁盘的inode节点在内存的缓存
  • 目录项 dentry 磁盘上的目录项在内存的缓存
  • 文件对象 file 表示被进程的打开的文件

关系图#

ditaa-29f54aaa1a85b819ff29cb7d101a4d646b3b0b06 多个文件描述符可以指向同一个文件,因为可以使用dup系统调用来复制一个文件描述符。 如果我们多次打开相同的路径,多个文件可以指向相同的 dentry 当使用硬链接时,多个 dentry 可以指向同一个 inode

PageCache#

  • 内核维护一部分内存作为磁盘cache
  • 预先读 读操作检查是否在cache命中,命中省去io不命中则发起写操作,对于顺序读操作io的局部性很强可以把接下来的内容也一起读出来放入cache。对于随机访问预先读的作用不是很明显。是否采用预读操作系统是不能预测的只有进程主动告知了,可以采用的方法有madvise()和posix_fadvise(),前者主要配合基于文件的mmap映射使用
  • 延迟写 写入操作则是简单地把数据写入cache,几次顺序的写操作可以合并成一个更大的写操作
  • 同步时机
    • 当系统中”dirty”的内存大于某个阈值时
    • 内核线程定期把脏cache项写回磁盘
    • 用户主动发起sync调用
    • 一般不等到内存满再去同步到磁盘
  • O_SYNC 开启write through模式
  • O_DIRECT 可以绕过内核缓存
  • page cache在内核是VFS层做的

空闲空间管理#

  • 空闲表
  • 空闲链表
  • 位向量
  • 成组链接法 成组连接法注意最后一组的最后一块是NULL,做为空闲盘块链的结束标志所以可用块数要减去这个无效盘块
  • FAT

其他#

open、dup、fork的区别#

示例文件

abcdefg

dup只是复制一个文件打开表项,表项指向相同的file对象故共享读写指针的位置

#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
  __auto_type fd = open("test", O_RDONLY);
  char c;
  read(fd, &c, 1);
  assert(c == 'a');
  __auto_type dupped = dup(fd);
  read(dupped, &c, 1);
  assert(c == 'b');
}

每次open会构造一个file对象,open相同文件的构造出来的file对象都指向相同的inode对象,因此用open系统调用得到的fd有各自的读写指针

#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
  __auto_type fd0 = open("test", O_RDONLY);
  __auto_type fd1 = open("test", O_RDONLY);
  char c;
  read(fd0, &c, 1);
  assert(c == 'a');
  read(fd1, &c, 1);
  assert(c == 'a');
}

fork系统调用的父子进程共享同一个文件描述符,因此他们共享相同的file对象,相同的读写指针

#include <assert.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
  char c;
  __auto_type fd = open("test", O_RDONLY);
  __auto_type pid = fork();
  if (pid == 0) {
    read(fd, &c, 1);
    assert(c == 'a');
    return 0;
  }
  wait(NULL);
  read(fd, &c, 1);
  assert(c == 'b');
}

在进程打开文件删除文件时会怎么样#

linux内核inode对象维护了硬链接计数跟打开计数,删除名字只是减少inode对象的硬链接计数,只有当硬链接计数跟打开计数都降为零时才删除文件

与磁盘结合复习#

机械硬盘

持久化
https://blog.pipago360.site/posts/操作系统/持久化/
作者
Ashenye
发布于
2023-10-04
许可协议
CC BY-NC-SA 4.0