欢迎光临散文网 会员登陆 & 注册

操作系统导论-持久化

2023-07-19 22:16 作者:Y沁园  | 我要投稿

第36章I/O设备

36.1系统架构

从下到上性能越来越高,成本越来越高,支持连接的设备数量越来越少。

36.2标准设备

一个标准设备包含两部分:

  • 硬件接口:供系统软件控制它的操作,所有设备都有自己的特定接口以及典型交互协议。

  • 内部结构:设备相关功能的特定实现,负责具体实现设备展示给系统的抽象接口。

状态寄存器:读取设备当前状态;命令寄存器:通知设备执行具体任务;数据寄存器:将数据传给设备/从设备接收数据。

36.3标准协议

协议:操作系统如何与设备进行交互

36.4利用中断减少CPU开销

如果设备非常快->轮询;如果设备比较慢->中断。

设备的速度未知使用混合(hybrid)策略,先尝试轮询一小段时间,如果设备没有完成操作,此时再使用中断。这种两阶段(two-phased)的办法可以实现两种方法的好处。

36.5利用DMA进行更高效的数据传送

使用 PIO的方式,CPU的时间会浪费在向设备传输数据或从设备传出数据的过程中。

DMA工作过程如下。OS通过编程告诉DMA引擎数据在内存的位置,要拷贝的大小以及要拷贝到那个设备。此后操作系统可以处理其它请求。当DMA的任务完成后,DMA控制器抛出一个中断来告诉OS已经完成数据传输。

36.6设备交互的方法

硬件如何与设备通信?

  • 特权指令:采用明确的I/O指令,这些指令规定了操作系统将数据送到特定设备寄存器的方法。

  • 内存映射I/O:硬件将设备寄存器作为内存地址提供。当需要访问设备寄存器时,操作系统装载(读取)或者存入(写入)到该内存地址;如何硬件会将装载/存入转移到设备上,而不是物理内存。

特权指令中的特权指OS是唯一可以直接与设备交互的实体。

内存映射I/O的好处是不需要引入新指令来实现设备交互。

36.7纳入OS:设备驱动程序

每个设备都有非常具体的接口,如何将它们纳入操作系统,我们希望操作系统尽可能通用。

关键问题:如何实现一个设备无关的操作系统 ?

如何保持操作系统的大部分与设备无关,从而对操作系统的主要子系统隐藏设备交互的细节? 通过抽象进行解决。

在最底层,操作系统的一部分软件清楚地知道设备如何工作,我们将这部分软件称为设备驱动程序(device driver),所有设备交互的细节都封装在其中。

文件系统(当然也包括在其之上的应用程序)完全不清楚它使用的是什么类型的磁盘。它只需要简单地向通用块设备层发送读写请求即可,块设备层会将这些请求路由给对应的设备驱动,然后设备驱动来完成真正的底层操作。

这种封装也有不足的地方。例如,如果有一个设备可以提供很多特殊的功能,但为了兼容大多数操作系统它不得不提供一个通用的接口,这样就使得自身的特殊功能无法使用。

第37章I/O设备

关键问题:现代磁盘驱动器如何存储数据?接口是什么?数据是如何安排和访问的?磁盘调度如何提高性能?

37.1接口

驱动器由大量山区(512字节块)组测,磁盘可以视为一组扇区。

许多文件系统一次读取或写入4KB(或更多),驱动器制造商唯一保证的是单个512字节的写入是原子的。

37.2基本几何形状

37.3简单的磁盘驱动器

磁盘完整的I/O时间图:寻道;盘片旋转;数据传输;

许多驱动器采用某种形式的磁道偏斜(track skew),以确保即使在跨越磁道边界时,顺序读取也可以方便地服务。

扇区往往会偏移因为从一个磁道切换到另一个磁道时,磁盘需要时间来重新定位磁头(即便移到相邻磁道)。如果没有这种偏斜,磁头将移动到下一个磁道,但所需的下一个块已经旋转到磁头下,因此驱动器将不得不等待整个旋转延迟,才能访问下一个块。

后写:写入磁盘缓存后,回报写入完成。

直写:实际写入磁盘后,回报写入完成。

后写缓存有时会使驱动器看起来"更快",但可能有危险。

37.4I/O时间

第38章廉价冗余磁盘阵列RAID

关键问题:我们如何构建一个大型、快速和可靠的存储系统?关键技术是什么?不同方法之间的折中是什么?

RAID使用多个磁盘一起构建更快、更大、更可靠的磁盘系统。

从外部看,RAID看起来像一个磁盘:一组可以读取或写入的块。

在内部,RAID由多个磁盘、内存(包括易失性和非易失性)以及一个或多个处理器来管理系统。

RAID的优点:

  • 性能:并行使用多个磁盘可以大大加快 I/O时间。

  • 容量:大型数据集需要大型磁盘。

  • 可靠性:通过某种形式的冗余(redundancy),RAID可以容许损失一个磁盘并保持运行,就像没有错误一样。

  • 透明性:RAID对于主机系统看起来就像一个大磁盘。

38.1接口和RAID内部

当文件系统向 RAID发出逻辑 I/O请求时,RAID内部必须计算要访问的磁盘(或多个磁盘)以完成请求,然后发出一个或多个物理 I/O来执行此操作。

在很高的层面上来看,RAID是一个非常专业的计算机系统:它有一个处理器,内存和磁盘。然而,它不是运行应用程序,而是运行专门用于操作 RAID的软件。

38.2故障模型

RAID旨在检测并从某些类型的磁盘故障中恢复。

38.3如何评估RAID

性能;可靠性;容量;

我们现在考虑 3个重要的 RAID设计:RAID 0级(条带化),RAID 1级(镜像)和 RAID 4/5级(基于奇偶校验的冗余)

38.4RAID0级:条带化

没有冗余,但可作为性能和容量的上限。

RAID阵列的大块大小(chunk size)为4KB

RAID阵列的大块大小(chunk size)为8KB

RAID映射问题

给定一个逻辑块来读或写,RAID如何确切地知道要访问哪个物理磁盘和偏移量?

对于块大小为8KB的情况:磁盘= A % 磁盘数 ;偏移量 = A / 磁盘数 ;

大块大小的tradeoff

chunk size较小的情况:文件将跨多个磁盘进行条带化,从而增加了对单个文件的读取和写入的并行性。但是,跨多个磁盘访问块的定位时间会增加,因为整个请求的定位时间由所有驱动器上请求的最大定位时间决定。

chunk size较大的情况:较大的大块大小减少了这种文件内的并行性,因此依靠多个并发请求来实现高吞吐量。但是,较大的大块大小减少了定位时间。

大多数RAID采用64KB大小的chunk

评估RAID的性能:考虑两种性能指标

  • 单请求延迟:它可以揭示单个逻辑 I/O操作期间可以存在多少并行性。

  • RAID稳态吞吐量:即许多并发请求的总带宽。

单块请求的延迟与单个磁盘的延迟几乎相同;

稳态吞吐量对于顺序IO=N(磁盘数量)×S(单个磁盘的顺序带宽);随机IO=N×R(单个磁盘的随机带宽)

38.5RAID1级:镜像

RAID1(镜像):需生成系统中每个块的多个副本。每个副本应该放在一个单独的磁盘上。这样,我们可以容许磁盘故障。

假设对于每个逻辑块,RAID保留两个物理副本。

读取可以选择一个进行读取,写入可以并行进行。

RAID-1分析

容量在镜像级别=2时,容量=N/2;从可靠性角度,表现良好。

性能:

  • 单请求延迟:读取与单个磁盘上的延迟相同。写入时间大致等于单次写入时间,平均而言比写入单个磁盘略高。

  • 稳态吞吐量:顺序写入N/2×S;顺序读N/2×S;随机读N×R;随机写N/2×R;

38.6RAID4级:通过奇偶校验节省空间

向磁盘阵列添加冗余(奇偶校验),基于奇偶校验的方法试图使用较少的容量,从而克服由镜像系统付出的巨大空间损失。不过,这样做的代价是——性能。

奇偶校验=异或函数,如果有奇数个1,则返回1,否则返回0;

利用奇偶校验从故障中恢复:将数据位和奇偶校验位异或。

对于block,将block上的每一位按位执行XOR操作,将结果放入奇偶校验块的相应位置中。

RAID-4分析

  • 容量:RAID-4使用 1个磁盘作为它所保护的每组磁盘的奇偶校验信息。因此,RAID组的有用容量是(N−1)。

  • 可靠性:RAID-4容许 1个磁盘故障,不容许更多。如果丢失多个磁盘, 则无法重建丢失的数据。

  • 性能:连续读取性能可以利用除奇偶校验磁盘以外的所有磁盘,因此可提供(N−1)×S(简单情况)的峰值有效带宽。

将大块数据写入磁盘时,RAID-4可以执行一种简单优化,称为全条带写入(full-stripe write)。在这种情况下,RAID可以简单地计算 P0的新值(通过在块 0、12和 3上执行 XOR),然后将所有块(包括奇偶块)并行写入上面的 5个磁盘。因此,全条带写入是 RAID-4写入磁盘的最有效方式。

顺序写入的性能:有效带宽(N-1)×S;随机读性能:(N-1)×R;

随机写入:随机写的复写,会导致奇偶校验块 P0将不再准确地反映条带的正确奇偶校验值。P0也必须更新。

  • 加法奇偶校验:并行读取条带中所有其他数据块并与新块进行异或。生成新的校验块。接下来将新数据和新奇偶校验并行写入其各自的磁盘。(需要读取其它磁盘来计算奇偶校验)。

  • 减法奇偶校验:

使用减法奇偶校验,每次随机写入RAID必须执行4次物理I/O(两次读和两次写入)。

现几乎同时向 RAID-4提交 2个小的请求,这些数据位于磁盘 0和 1上,因此对数据的读写操作可以并行进行,出现的问题是奇偶校验磁盘。这两个请求都必须读取 4和 13的奇偶校验块,即奇偶校验块1和 3。

在这种类型的工作负载下,奇偶校验磁盘是瓶颈。我们有时将它称为基于奇偶校验的 RAID的小写入问题(small-write problem)。 因此,即使可以并行访问数据磁盘奇偶校验磁盘也不会实现任何并行。

奇偶校验磁盘必须为每个逻辑 I/O执行两次 I/O(一次读取,一次写入),其性能固定为R/2,添加磁盘也无法改善。

单请求延迟:单次写入的延迟需要两次读取,然后两次写入。读操作可以并行进行,写操作也是如此,因此总延迟大约是单个磁盘的两倍。

38.7 RAID5级:旋转奇偶校验

为解决小写入问题(部分解决),

顺序读写性能(N-1)×S;随机读N×R;小写入总带宽N×R/4;

38.8RAID比较:总结

第39章文件和目录

39.3创建文件

通过open系统调用完成,调用open()并传入O_CREAT标志,程序可以创建一个新文件。O_TRUNC,将其截断为零字节大小,删除所有现有内容。

open()的一个重要方面是它的返回值:文件描述符(file descriptor)。

一个文件描述符就是一种权限/指向文件类型对象的指针

39.4读写文件

open();read(文件描述符,缓冲区,缓冲区大小);write(文件描述符,缓冲区,写入大小);

39.5读取和写入,但不按顺序

off_t lseek(int fildes, off_t offset, int whence);  第一个参数是熟悉的(一个文件描述符)。第二个参数是偏移量,它将文件偏移量定位到文件中的特定位置。

偏移量决定在文件中读取或写入时,下一次读取或写入开始的位置

第一种是当发生 N个字节的读或写时,N被添加到当前偏移。因此,每次读取或写入都会隐式更新偏移量。第二种是明确的lseek,它改变了上面指定的偏移量。

39.6用fsync()立即写入

当程序调用 write()时,它只是告诉文件系统:请在将来的某个时刻,将此数据写入持久存储。出于性能的原因,文件系统会将这些写入在内存中缓冲(buffer)一 段时间(例如 5s或 30s)。在稍后的时间点,写入将实际发送到存储设备。从调用应用程序的角度来看,写入似乎很快完成,并且只有在极少数情况下(例如,在 write()调用之后但写入磁盘之前,机器崩溃)数据会丢失。

提供给应用程序的接口被称为 fsync(int fd)。当进程针对特定文件描述符调fsync()时,文件系统通过强制将所有脏(dirty)数据(即尚未写入的)写入磁盘来响应,针对指定文件描述符引用的文件。一旦所有这些写入完成,fsync()例程就会返回。

39.7文件重命名

mv使用了系统调用 rename(char * old, char * new),它只需要两个参数:文件的原来名称(old)和新名称(new)。

rename()调用是一个原子调用

39.8获取文件信息

文件系统会保存关于它存储的每个文件的metadata,可以使用stat();fstat()系统调用。通常保存在inode结构中。

39.9删除文件

使用unlink()系统调用,而不是remove/delete。

39.10创建目录

永远不能直接写入目录。因为目录的格式被视为文件系统元数据,所以只能间接更新目录,例如,通过在其中创建文件、目录或其他对象类型。

39.11读取目录

opendir(),readdir(),closedir();

目录只有少量的信息(基本上,只是将名称映射到 inode号

39.12删除目录

删除目录可能会导致删除大量的数据,因此rmdir()要求该目录在删除之前是空的。

39.13硬链接

39.14符号链接

第41章局部性和快速文件系统

41.1问题:性能不佳

UNIX文件系统性能很糟糕,主要问题是老 UNIX文件系统将磁盘当成随机存取内存。数据遍布各处,而不考虑保存数据的介质是磁盘的事实,因此具有实实在在的、昂贵的定位成本。例如,文件的数据块通常离其 inode非常远,因此每当第一次读取 inode然后读取文件的数据块。

文件系统会变得非常碎片化,因为空闲空间没有得到精心管理。空闲列表最终会指向遍布磁盘的一堆块。

需要磁盘碎片化整理工具,将重新组织磁盘数据以连续放置文件,并为让空闲空间成为一个或几个连续的区域,移动数据,然后重写 inode等以反映变化。

41.2FFS:磁盘意识文件系统

思路是让文件系统的结构和分配策略具有“磁盘意识”,从而提高性能。

所有现代文件系统都遵循现有的接口(从而保持与应用程序的兼容性),同时为了性能、可靠性或其他原因,改变其内部实现。

41.3组织结构:柱面组

FFS将磁盘划分为一些分组,称为柱面组。

这些分组是 FFS用于改善性能的核心机制。通过在同一组中放置两个文件,FFS可以确保先后访问两个文件不会导致穿越磁盘的长时间寻道。

FFS需要能够在每个组中分配文件和目录。每个组看起来像这样

41.4策略:如何分配文件和目录

相关的东西放一起(无关的东西分开放)

目录的放置:找到分配数量少的柱面组(因为我们希望跨组平衡目录)和大量的自由 inode(因为我们希望随后能够分配一堆文件),并将目录数据和 inode放在该分组中。当然,这里可以使用其他推断方法(例如,考虑空闲数据块的数量)。

文件的放置:首先将文件的数据块分配到与其inode相同的组中,从而防止 inode和数据之间的长时间寻道。其次,它将位于同一目录中的所有文件,放在它们所在目录的柱面组中。

41.5测量文件的局部性

路径差异值:为了找到两个文件的共同祖先,必须在目录树上走多远。它们在树上越靠近,度量值越低。

可以看到大约 7%的文件访问是先前打开的文件,并且近 40%的文件访问是相同的文件或同一目录中的文件(即 0或 1的差异值)。因此,FFS的局部性假设似乎有意义(至少对于这些跟踪)。

41.6大文件例外

如果没有不同的规则,大文件将填满它首先放入的块组(也可能填满其他组)。以这种方式填充块组是不符合需要的,因为它妨碍了随后的“相关”文件放置在该块组内,因此可能破坏文件访问的局部性。

对于大文件,FFS执行以下操作。在将一定数量的块分配到第一个块组之后,FFS将文件的下一个“大”块(即第一个间接块指向的那些部分)放在另一个块组中(可能因为它的利用率低而选择)。

FFS采用了一种简单的方法,基于 inode本身的结构。前 12个直接块与 inode放在同一组中。每个后续的间接块,以及它指向的所有块都放在不同的组中。如果块大小为 4KB,磁盘地址是 32位,则此策略意味着文件的每 1024个块(4MB)放在单独的组中,唯一的例外是直接指针所指向的文件的前 48KB。

为了摊销成本,必须在寻道之间传输更多数据。

41.7关于FFS的其它几件事

容纳小文件可能会导致磁盘浪费,当时许多文件大小为2KB左右,块大小4KB。

为此引入了子块,子块大小为512Byte,文件系统可以将子块分配给文件。直到其达到完整的4KB数据。此时FFS找到应该4KB块,将其复制到其中,并释放子块以备将来使用。

这个过程效率低下,FFS通过修改libc库来避免这种异常行为,将缓冲写入,然后以4KB块的形式将它们发送到文件系统。

第二个巧妙方法是针对性能进行优化的磁盘布局。

FFS首先发出一个请求,读取块 0。当读取完成时,FFS向块 1发出读取,为时已晚:块 1已在磁头下方旋转,现在对块 1的读取将导致完全旋转。

在下一块经过磁头之前,FFS有足够的时间发出请求。实际上,FFS足够聪明,能够确定特定磁盘在布局时应跳过多少块,以避免额外的旋转。这种技术称为参数化。

现代磁盘在内部读取整个磁道并将其缓冲在内部磁盘缓存中。

FFS是允许长文件名的第一个文件系统之一,符号链接,原子rename()操作。

第42章崩溃一致性:FSCK和日志

系统可能在任何两次写入之间崩溃或断电,因此磁盘上状态可能仅部分地更新。崩溃后,系统启动并希望再次挂载文件系统(以便访问文件等)。鉴于崩溃可能发生在任意时间点,如何确保文件系统将磁盘上的映像保持在合理的状态?

FSCK,文件系统检查程序;预写日志journaling;

42.2FSCK文件系统检查程序

让不一致的事情发生,然后重启时查找这些不一致并修复它们。

这种方法无法解决所有问题,文件系统看起来是一致的,但是inode指向垃圾数据。

FSCK的基本总结。

  • 超级块:fsck首先检查超级块是否合理,主要是进行健全性检查,例如确保文件系统大小大于分配的块数。通常,这些健全性检查的目的是找到一个可疑的(冲突的)超级块。在这种情况下,系统(或管理员)可以决定使用超级块的备用副本。

  • 空闲块:fsck扫描 inode、间接块、双重间接块等,以了解当前在文件系统中分配的块。它利用这些知识生成正确版本的分配位图。因此,如果位图和inode之间存在任何不一致,则通过信任 inode内的信息来解决它。对所有 inode执行相同类型的检查,确保所有看起来像在用的 inode,都在 inode位图中有标记。

  • inode状态:检查每个 inode是否存在损坏或其他问题。例如,fsck确保每个分配的 inode具有有效的类型字段(即常规文件、目录、符号链接等)。如果 inode字段存在问题,不易修复,则 inode被认为是可疑的,并被 fsck清除,inode位图相应地更新。

  • inode链接:fsck还会验证每个已分配的 inode的链接数。你可能还记得,链接计数表示包含此特定文件的引用(即链接)的不同目录的数量。为了验证链接计数,fsck从根目录开始扫描整个目录树,并为文件系统中的每个文件和目录构建自己的链接计数。如果新计算的计数与 inode中找到的计数不匹配,则必须采取纠正措施,通常是修复 inode中的计数。如果发现已分配的 inode但没有目录引用它,则会将其移动到 lost + found目录。

  • 重复:fsck还检查重复指针,即两个不同的 inode引用同一个块的情况。如果一个 inode明显不好,可能会被清除。或者,可以复制指向的块,从而根据需要为每个 inode提供其自己的副本。

  • 坏块:在扫描所有指针列表时,还会检查坏块指针。如果指针显然指向超出其有 效范围的某个指针,则该指针被认为是“坏的”,例如,它的地址指向大于分区大小的块。在这种情况下,fsck不能做任何太聪明的事情。它只是从 inode或间接块中删除(清除)该指针。

  • 目录检查:fsck不了解用户文件的内容。但是,目录包含由文件系统本身创建的特定格式的信息。因此,fsck对每个目录的内容执行额外的完整性检查,确保“.”和“..”是前面的条目,目录条目中引用的每个 inode都已分配,并确保整个层次结构中没有目录的引用超过一次。

根本问题:太慢了。

42.3日志(预写日志)

将更新的内容,先写入磁盘的一个指定区域(日志).

带有日志的ext3文件系统如下

数据日志

中间的 3个块只包含块本身的确切内容,这被称为物理日志(physical logging)。

在日志中放置更紧凑的更新逻辑,逻辑日志,

一旦这个事务安全地存在于磁盘上,我们就可以覆写文件系统中的旧结构了。这个过程称为加检查点(checkpointing)。如果写入成功完成,说明成功的加上了检查点。

在写入日志期间发生崩溃,当一次发出5个块的写入。

简单的方法是一次发出一个,等待每个完成再发出下一个。

通过写入屏障(wirte barrier),当它完成时,能确保在屏障之前发出的写入,先于在屏障之后发出的写入到达磁盘。

可能会将垃圾快??的内容复制导DB应该存在的位置上。

写入日志,必须先写出事务开始块和内容,这些写入完成后,文件系统才能将事务结束块发送到磁盘。磁盘会产生额外的旋转,对性能影响很明显。

为了避免一次发出5个块写入的问题,文件系统分两步发出事务写入。

日志写入;日志提交;加检查点;

恢复:如果崩溃发送在事务被安全地写入日志前,只需简单地跳过待执行的更新。

如果事务已提交到日志之后但在加检查点完成之前发生崩溃,文件系统恢复过程将扫描日志,并查找已提交到磁盘的事务。然后,这些事务被重放(replayed,按顺序),文件系统再次尝试将事务中的块写入它们最终的磁盘位置。

批处理日志

基本协议可能会增加大量额外的磁盘流量。

在同一目录中连续创建两个文件,称为 file1和 file2。要创建一个文件,必须更新许多磁盘上的结构,它们需要一遍又一遍的写入相同的块。

一些文件系统不会一次一个地向磁盘提交每个更新(例如,Linux ext3)。通过将所有更新缓冲到全局事务中。

文件系统只将内存中的 inode位图、文件的 inode、目录数据和目录 inode标记为脏,并将它们添加到块列表中,形成当前的事务。当最后应该将这些块写入磁盘时(例如,在超时 5s之后),会提交包含上述所有更新的单个全局事务。因此,通过缓冲更新,文件系统在许多情况下可以避免对磁盘的过多的写入流量。

使日志有限

日志满会导致两个问题:日志越大恢复时间越长,日志已满时不能向磁盘提交进一步的事务。采用循环数据结构。

元数据日志

对于每次写入磁盘,我们现在也要先写入日志,从而使写入流量加倍。

有序日志/元数据日志,用户数据不需写入日志,直接写入文件系统。

在将相关元数据写入磁盘日志前,先将数据块写入磁盘。

通过强制先写入数据,文件系统可以保证指针永远不会指向垃圾。这个“先写入被指对象,再写入指针对象”的规则是崩溃一致性的核心,

发出写入日志之前强制数据写入完成不是正确性所必需的。真正的要求是发出日志提交快之前完成数据写入和日志元数据写入。


42.4其它方法

软更新:软更新[GP94]的方法。这种方法仔细地对文件系统的所有写入排序,以确保磁盘上的结构永远不会处于不一致的状态。例如,通过先写入指向的数据块,再写入指向它的inode,可以确保 inode永远不会指向垃圾。

写时复制:这种技术永远不会覆写文件或目录。相反,它会对磁盘上以前未使用 的位置进行新的更新。在完成许多更新后,COW文件系统会翻转文件系统的根结构,以包含指向刚更新结构的指针。这样做可以使文件系统保持一致。

减少日志协议等待磁盘写入完成的次数的技术。这种新方法名为乐观崩溃一致性(optimistic crash consistency)

尽可能多地向磁盘发出写入,并利用事务校验和(transaction checksum)的一般形式,以及其他一些技术来检测不一致,如果出现不一致的话。

第43章日志结构文件系统

——To Be Continue


操作系统导论-持久化的评论 (共 条)

分享到微博请遵守国家法律