OSTEP-操作系统导论-文件系统与存储设备
操作系统将物理资源抽象为虚拟资源,例如将内存抽象为虚拟内存、CPU和内存抽象为进程;而对于磁盘,操作系统则以文件的形式加以抽象。
文件是磁盘的抽象。应用程序不会直接读写挂载在计算机上的磁盘本身,操作系统中也是不允许的。在Unix中,与存储系统交互的唯一方式就是通过文件:可以命名文件、读写文件等。在这背后,操作系统负责决定如何将文件映射到磁盘块,确保一个磁盘块只属于一个文件,并确保用户A无法操作用户B的文件。
用单一CPU抽象出无限个vCPU采用的是时分复用(time sharing),而用单一磁盘抽象出多个文件则是空分复用(space sharing)。
文件系统的目标是使用下层的持久存储设备作为资源,为上层提供文件抽象及相应的读写操作。
1.1 抽象:文件和目录
文件是一个线性的字节数组。文件系统中,文件的抽象与inode对应,inode由inode号(inumber)标识,inode包含(一级或多级)索引,这些索引直接或间接指向文件对应的数据块。
目录包含若干个文件或下级目录。文件系统中,目录的抽象也与inode对应,对应的inode同样有inumber,inode包含索引,指向目录数据块,目录数据块包含“所含文件或下级目录的名称->所含文件或下级目录的inode”的映射列表。
最高级的目录是根目录。根目录包含文件或下级目录,递归地组成整个文件系统。根目录的inode号应是已知的,因此,通过根目录,可以查找文件系统中的任何文件。
Unix 文件系统的API
- open: 从根目录开始,递归查找(或创建)指定文件路径,返回文件描述符fd(文件描述符由进程私有)。
- read: 给定文件描述符,读取指定长度的数据,并隐式更新offset(由操作系统跟踪,与文件描述符绑定,即系统跟踪当前进程在打开的文件中下一个读写位置)。
- write: 给定文件描述符,写入指定长度的数据(到内存缓冲区,并在适当时写回磁盘),并隐式更新offset。
- lseek: 给定文件描述符,用参数指定的值(和方式)修改offset。
- fsync: 立即将文件写入磁盘(write可能不会立即写入磁盘),成功后返回。
- link(path1, path2): 创建硬链接,在path2目录的映射列表中创建一个新条目,指向path1文件的inode,并增加inode中的引用计数。
- unlink: 删除文件,即删除当前目录中文件与inode之间的映射条目,减少inode中的引用计数,inode和文件数据只有在引用计数为0时才会真正删除。
- rename: 文件重命名。
- stat/fstat: 查看文件元数据。
- mkdir: 创建目录。
- opendir/readdir/closedir: 打开/读取/关闭目录。
- rmdir: 删除空目录。
- mkfs: 创建文件系统。
- mount: 挂载文件系统,将目录树复制粘贴。
此外,还可以使用“ln -s”命令创建符号链接,符号链接实际上是一个特殊文件,指向已存在的文件。
1.2 存储设备:磁盘
1.2.1 外部设备
一个标准的外部设备包括硬件接口和内部实现。接口上一系列寄存器用于与系统交换状态、命令和数据,内部实现依赖于硬件本身,通常包含硬件的CPU、内存等芯片。
硬件与系统的交互方式包括:轮询(polling, CPU询问硬件是否有数据要传输)、中断(interrupt, 硬件向CPU发出中断请求)、杂合(先轮询后中断)、DMA(直接内存存取,硬件数据直接复制至内存,无需CPU参与,完成后中断)。
设备与系统通信的方法有:特权指令(使用in/out特权指令传输数据)、内存映射IO(MMIO,设备直接映射到虚拟内存中)。
设备通过抽象接口连接到应用程序,例如文件系统栈中,应用程序使用文件系统提供的POSIX API(如上所述)。文件系统使用操作系统的通用块接口读取数据块,而操作系统通过设备驱动提供的具体块接口与硬件交互。
1.2.2 磁盘
磁盘是一种常见的存储设备,广泛应用于计算机系统中。传统的机械硬盘(HDD)由旋转的磁盘盘片和读写磁头组成,数据通过磁头在盘片上的磁性表面进行读写操作。硬盘的性能主要由转速(RPM)、平均寻道时间和数据传输速率决定。
-
分区与格式化: 磁盘通常被划分为多个分区,每个分区可以独立地被操作系统管理。在使用之前,磁盘需要经过格式化,创建文件系统,使其能够存储和管理文件。
-
存储结构: 磁盘存储数据的基本单位是扇区(sector),通常为512字节或4KB。多个扇区组成一个块(block),文件系统以块为单位进行管理。
-
寻道与延迟: 由于机械硬盘的物理特性,寻道时间(磁头移动到目标扇区的时间)和旋转延迟(等待目标扇区旋转到磁头下的时间)是影响磁盘性能的重要因素。
随着技术的发展,固态硬盘(SSD)逐渐普及。SSD没有机械部件,数据存储在闪存芯片上,具有更高的读写速度和更低的延迟。SSD通过控制器管理数据的读写,并采用磨损均衡(wear leveling)技术,延长闪存芯片的使用寿命。
1.3 文件系统实现方案
VSFS
VSFS(Very Simple File System)是一种教学用途的文件系统,用于帮助理解文件系统的基本概念。它使用了一种非常简单的结构:
- 超级块: 存储文件系统的元数据,如文件系统的大小、空闲块数等。
- inode表: 存储文件的元数据,如文件的大小、指向数据块的指针等。
- 数据块: 存储文件的实际数据。
VSFS的设计非常简单,缺乏现代文件系统的一些特性,如日志(journaling)和多级索引,但其简单性有助于学习文件系统的基本工作原理。
FFS
FFS(Fast File System)是Unix系统中的一种重要文件系统,针对性能进行了优化。它采用了以下几个关键技术:
- 块分组(block grouping): 将文件系统划分为多个块组,每个块组包含若干个连续的数据块、inode表和一个超级块副本。这种结构减少了磁头的移动,提升了性能。
- 多级索引(multi-level indexing): FFS使用了直接、间接、双重间接和三重间接索引,支持存储非常大的文件。
- 文件系统碎片管理: 通过合理分配数据块,减少文件系统碎片,提高磁盘利用率。
LFS
LFS(Log-Structured File System)是一种针对写操作进行了优化的文件系统。其主要特点包括:
- 日志结构: 所有数据和元数据都以日志形式追加到磁盘的末尾,从而避免了频繁的磁盘寻道操作,提升了写性能。
- 清理机制(cleaning mechanism): 由于日志结构会导致旧数据块的存在,LFS通过清理机制回收这些旧块,确保磁盘空间的有效利用。
LFS适用于写操作频繁的应用场景,如数据库系统和高性能计算。
Linux的方案:Ext3/4
Ext3/4是Linux操作系统中的主流文件系统,继承了Ext2的设计,并引入了一些现代文件系统的特性:
- 日志(journaling): Ext3/4支持日志记录,有效提高了文件系统的可靠性。日志记录可以在崩溃后快速恢复文件系统状态。
- 延迟分配(delayed allocation): Ext4引入了延迟分配策略,数据块的实际分配推迟到写入磁盘前,从而优化了磁盘布局,减少了碎片。
- 扩展属性(extended attributes): 支持为文件和目录存储额外的元数据,如访问控制列表(ACLs)。
- 大文件支持: Ext4支持更大的文件和分区,并且通过多级索引,能够高效地管理大文件。
多核心
多核心调度
多核心调度是现代操作系统中的一个重要挑战,涉及如何有效地将任务分配给多个处理器核心。常见的多核心调度策略包括:
- 负载均衡(load balancing): 通过在核心之间均匀分配任务,避免某些核心过载,而其他核心空闲。操作系统可以使用全局队列或每个核心的本地队列来管理任务。
- 亲和性调度(affinity scheduling): 将任务分配给最近执行过该任务的核心,利用缓存的局部性,提升性能。亲和性调度可以分为软亲和性和硬亲和性,软亲和性允许任务在不同核心之间迁移,而硬亲和性则限制任务只能在指定核心上执行。
- 抢占式调度(preemptive scheduling): 在多任务环境中,操作系统可以强制切换任务,以确保高优先级任务及时得到执行。
多核心并发
多核心并发涉及如何在多个处理器核心之间安全地共享数据和资源。常见的多核心并发控制机制包括:
- 锁(lock): 用于保护临界区,确保同一时间只有一个核心可以访问共享资源。常见的锁机制包括自旋锁(spinlock)和互斥锁(mutex)。
- 原子操作(atomic operation): 硬件支持的原子操作能够在多核心环境中安全地修改共享变量,避免竞争条件。
- 内存屏障(memory barrier): 保证在多核心系统中,指令的执行顺序不会因硬件优化而乱序,确保并发操作的正确性。
- 并行编程模型: 常见的并行编程模型包括线程(thread)、任务(task)和消息传递(message passing)。这些模型通过不同的方式管理并发执行的任务,提升程序的性能。