1414
1515.. note ::
1616
17- 文件系统在UNIX操作系统有着特殊的地位,根据史料《UNIX: A History and a Memoir》记载,1969年,Ken Thompson(Unix的作者 )在贝尔实验室比较闲,写了PDP-7计算机的磁盘调度算法来提高磁盘的吞吐量。为了测试这个算法,他本来想写一个批量读写数据的测试程序。但写着写着,他在某一时刻发现,这个测试程序再扩展一下,就是一个文件系统了,再再扩展一下 ,就是一个操作系统了。他的自觉告诉他,他离实现一个操作系统仅有 **三周之遥 ** 。一周:写代码编辑器;一周:写汇编器;一周写shell程序,在写这些程序的同时,需要添加操作系统的功能(如 exec等系统调用)以支持这些应用。结果三周后,为测试磁盘调度算法性能的UNIX雏形诞生了。
17+ 文件系统在UNIX操作系统有着特殊的地位,根据史料《UNIX: A History and a Memoir》记载,1969年,Ken Thompson(UNIX的作者 )在贝尔实验室比较闲,写了PDP-7计算机的磁盘调度算法来提高磁盘的吞吐量。为了测试这个算法,他本来想写一个批量读写数据的测试程序。但写着写着,他在某一时刻发现,这个测试程序再扩展一下,就是一个文件,再扩展一下 ,就是一个操作系统了。他的自觉告诉他,他离实现一个操作系统仅有 **三周之遥 ** 。一周:写代码编辑器;一周:写汇编器;一周写shell程序,在写这些程序的同时,需要添加操作系统的功能(如 exec等系统调用)以支持这些应用。结果三周后,为测试磁盘调度算法性能的UNIX雏形诞生了。
1818
1919
2020.. chyyuu 可以介绍文件系统 ???
4242 计算机的第一种存储方式是图灵设计的图灵机中的纸带。在计算机最早出现的年代,纸质的穿孔卡成为了第一代的数据物理存储介质。
4343 随着各种应用对持久存储大容量数据的需求,纸带和穿孔卡很快就被放弃,在计算机发展历史依次出现了磁带、磁盘、光盘、闪存等各种各样的外部存储器(也称外存、辅助存储器、辅存等)。与处理器可直接寻址访问的主存(也称内存)相比,处理器不能直接访问,速度慢1~2个数量级,容量多两个数量级以上,且便宜。应用软件访问这些存储设备上的数据很繁琐,效率也低,于是文件系统就登场了。这里介绍一下顺序存储介质(以磁带为代表)的文件系统和随机存储介质(以磁盘为代表)的文件系统。
4444
45- 磁带是一种顺序存储介质,磁带的历史早于计算机,它始于 1928 年,当时它被开发用于音频存储(就是的录音带 )。在1951 年,磁带首次用于在UNIVAC I计算机上存储数据。磁带的串行顺序访问特征对通用文件系统的创建和高效管理提出了挑战,磁带需要线性运动来缠绕和展开可能很长的介质卷轴。磁带的这种顺序运动可能需要几秒钟到几分钟才能将读/写磁头从磁带的一端移动到另一端。一个磁带文件系统是设计用于存储在磁带上的文件目录和文件。磁带文件系统通常允许将文件目录与文件数据分布在一起, 因此不需要耗时且重复的磁带往返线性运动来写入新数据。由于磁带容量很大,保存方便,还有就是很便宜 (磁带的成本比磁盘低一个数量级),所以到现在为止,磁带文件系统还在被需要存储大量数据的单位(如数据中心)使用。
45+ 磁带是一种顺序存储介质,磁带的历史早于计算机,它始于 1928 年,当时它被开发用于音频存储(就是录音带 )。在1951 年,磁带首次用于在UNIVAC I计算机上存储数据。磁带的串行顺序访问特征对通用文件系统的创建和高效管理提出了挑战,磁带需要线性运动来缠绕和展开可能很长的介质卷轴。磁带的这种顺序运动可能需要几秒钟到几分钟才能将读/写磁头从磁带的一端移动到另一端。磁带文件系统用于存储在磁带上的文件目录和文件,为提高效率,它通常允许将文件目录与文件数据分布在一起, 因此不需要耗时且重复的磁带往返线性运动来写入新数据。由于磁带容量很大,保存方便,且很便宜 (磁带的成本比磁盘低一个数量级),所以到现在为止,磁带文件系统还在被需要存储大量数据的单位(如数据中心)使用。
4646
47- 1956 年,IBM发布了第一款硬盘驱动器,硬盘的高速随机访问数据的能力使得它成为替代磁带的合理选择。文件系统是操作系统的重要组成部分。 在 Multics 之前,大多数操作系统最多提供复杂和不规则的文件系统来存储信息。 与当时的其他文件系统相比,Multics 文件系统更加通用、规则和强大,但它也相应地复杂。在Multics操作系统的设计中,主要面向磁盘这种辅助存储器, 文件只是一个字节序列。Multics操作系统第一次引入了层次文件系统的概念,即文件系统中的目录可以包含其他子目录,从而在理论和概念上描述了无限大的文件系统,并使得所有用户能够访问私人和共享文件。用户通过文件名来寻址文件并访问文件内容,这使得文件系统的基本结构独立于物理存储介质。文件系统可以动态加载和卸载,以便于数据存储备份等操作。可以说,Multics的这些设计理念(提出这些设计理念的论文出现在Multics操作系统完成的四年前)为Unix和后续操作系统对文件的管理指明了方向 。
47+ 1956 年,IBM发布了第一款硬盘驱动器,硬盘高速随机访问数据的能力使得它成为替代磁带的合理选择。 在 Multics 之前,大多数操作系统一般提供特殊且复杂的文件系统来存储信息。这里的特殊和复杂性主要体现在操作系统对面向不同应用的文件数据格式的直接支持上。 与当时的其他文件系统相比,Multics 文件系统不需要支持各种具体的文件数据格式,而是把文件数据看成是一个无格式的字节流,这样在一定程度上就简化了文件系统的设计。Multics操作系统的存储管理主要面向磁盘这种辅助存储器, 文件只是一个字节序列。Multics操作系统第一次引入了层次文件系统的概念,即文件系统中的目录可以包含其他子目录,从而在理论和概念上描述了无限大的文件系统,并使得所有用户能够访问私人和共享文件。用户通过文件名来寻址文件并访问文件内容,这使得文件系统的基本结构独立于物理存储介质。文件系统可以动态加载和卸载,以便于数据存储备份等操作。可以说,Multics的这些设计理念(提出这些设计理念的论文出现在Multics操作系统完成的四年前)为UNIX和后续操作系统中基于文件的存储管理指明了方向 。
4848
4949 眼中一切皆文件的UNIX文件系统
5050
51- 而Ken Thompson 在UNIX文件系统的设计和实现方面,采纳了Multics文件系统中的很多设计理念。Unix 文件只是一个字节序列。文件内容的任何结构或组织仅由处理它的程序决定。Unix文件系统本身并不关心文件的具体内容 ,这意味着任何程序都可以读写任何文件。这样就避免了操作系统对各种文件内容的没有必要的解析 ,极大地简化了操作系统的设计与实现。同时UNIX提出了“一切皆文件”的设计理念。你几乎可以想到的所有内容都可以通过文件系统中的文件来命名。 除了文件自身外,设备、管道、甚至网络、进程、内存空间都可以用文件来表示和访问。这种命名的一致性简化了操作系统的概念模型,使操作系统对外的接口组织更简单、更模块化。基本的文件访问操作包括 ``open read write close`` ,表示了访问一个文件最核心的操作 :打开文件、读文件内容、写文件内容和关闭文件。直到今天,原始 UNIX 文件系统中文件访问操作的语义几乎没有变化。
51+ 而Ken Thompson 在UNIX文件系统的设计和实现方面,采纳了Multics文件系统中的很多设计理念。UNIX 文件只是一个字节序列。文件内容的任何结构或组织仅由处理它的程序决定。UNIX文件系统本身并不关心文件的具体内容 ,这意味着任何程序都可以读写任何文件。这样就避免了操作系统对各种文件内容的解析 ,极大地简化了操作系统的设计与实现。同时UNIX提出了“一切皆文件”的设计理念,这使得你几乎可以想到的各种操作系统组件都可以通过文件系统中的文件来命名。 除了文件自身外,设备、管道、甚至网络、进程、内存空间都可以用文件来表示和访问。这种命名的一致性简化了操作系统的概念模型,使操作系统对外的接口组织更简单、更模块化。基本的文件访问操作包括 ``open, read, write, close`` ,表示了访问一个文件最核心和基础的操作 :打开文件、读文件内容、写文件内容和关闭文件。直到今天,原始 UNIX 文件系统中文件访问操作的语义几乎没有变化。
5252
5353
5454本章我们将实现一个简单的文件系统 -- easyfs,能够对 **持久存储设备 ** (Persistent Storage) 这种 I/O 资源进行管理。对于应用程序访问持久存储设备的需求,内核需要新增两种文件:常规文件和目录文件,它们均以文件系统所维护的 **磁盘文件 ** 形式被组织并保存在持久存储设备上。这样,就形成了具有强大UNIX操作系统基本功能的 “霸王龙” [#rex ]_ 操作系统。
118118 Shell: Process 2 exited with code 0
119119 >>
120120
121- 它会将 ``Hello, world! `` 输出到另一个文件 ``filea `` ,并读取里面的内容确认输出正确。我们也可以通过命令行工具 ``cat `` 来更直观的查看 ``filea `` 中的内容:
121+ 它会将 ``Hello, world! `` 输出到另一个文件 ``filea `` ,并读取里面的内容确认输出正确。我们也可以通过命令行工具 ``cat_filea `` 来更直观的查看 ``filea `` 中的内容:
122122
123123.. code-block ::
124124
125- >> cat filea
125+ >> cat_filea
126126 Hello, world!
127127 Shell: Process 2 exited with code 0
128128 >>
225225 ├── Makefile
226226 └── src
227227 ├── bin
228- │ ├── cat .rs(新增)
228+ │ ├── cat_filea .rs(新增)
229229 │ ├── cmdline_args.rs(新增)
230230 │ ├── exit.rs
231231 │ ├── fantastic_text.rs
256256 本章代码导读
257257-----------------------------------------------------
258258
259- 本章涉及的代码量相对较多,且与进程执行相关的管理还有直接的关系。其实我们是参考经典的UNIX基于索引的文件系统,设计了一个简化的有一级目录并支持创建 /打开/读写/关闭文件一系列操作的文件系统。这里简要介绍一下在内核中添加文件系统的大致开发过程。
259+ 本章涉及的代码量相对较多,且与进程执行相关的管理还有直接的关系。其实我们是参考经典的UNIX基于索引结构的文件系统,设计了一个简化的有一级目录并支持 `` open,read, write, close `` ,即创建 /打开/读写/关闭文件一系列操作的文件系统。这里简要介绍一下在内核中添加文件系统的大致开发过程。
260260
261261**第一步:是能够写出与文件访问相关的应用 **
262262
@@ -276,7 +276,7 @@ easyfs 文件系统的整体架构自下而上可分为五层:
276276
277277它的最底层就是对块设备的访问操作接口。在 ``easy-fs/src/block_dev.rs `` 中,可以看到 ``BlockDevice `` trait ,它代表了一个抽象块设备的接口,该 trait 仅需求两个函数 ``read_block `` 和 ``write_block `` ,分别代表将数据从块设备读到内存缓冲区中,或者将数据从内存缓冲区写回到块设备中,数据需要以块为单位进行读写。easy-fs 库的使用者(如操作系统内核)需要实现块设备驱动程序,并实现 ``BlockDevice `` trait 以提供给 easy-fs 库使用,这样 easy-fs 库就与一个具体的执行环境对接起来了。至于为什么块设备层位于 easy-fs 的最底层,那是因为文件系统仅仅是在块设备上存储的稍微复杂一点的数据。无论对文件系统的操作如何复杂,从块设备的角度看,这些操作终究可以被分解成若干次基本的块读写操作。
278278
279- 尽管在操作系统的最底层(即块设备驱动程序)已经有了对块设备的读写能力,但从编程方便/正确性和读写性能的角度来看,仅有块读写这么基础的底层接口是不足以实现复杂的文件系统的 。比如,某应用将一个块的内容读到内存缓冲区,对缓冲区进行修改,并尚未写回块设备时,如果另外一个应用再次将该块的内容读到另一个缓冲区,而不是使用已有的缓冲区,这将会造成数据不一致问题。此外还有可能增加很多不必要的块读写次数,大幅降低文件系统的性能。因此,通过程序自动而非程序员手动对块的缓冲区进行统一管理也就很必要了 ,该机制被我们抽象为 easy-fs 自底向上的第二层,即块缓存层。在 ``easy-fs/src/block_cache.rs`` 中, ``BlockCache`` 代表一个被我们管理起来的块缓冲区,它包含块数据内容以及块的编号等信息。当它被创建的时候,将触发一次 ``read_block`` 将数据从块设备读到它的缓冲区中。接下来只要它驻留在内存中,便可保证对于同一个块的所有操作都会直接在它的缓冲区中进行而无需额外的 ``read_block`` 。块缓存管理器 ``BlockManager`` 在内存中管理有限个 ``BlockCache`` 并实现了类似 FIFO 的缓存替换算法,当一个块缓存被换出的时候视情况可能调用 ``write_block`` 将缓冲区数据写回块设备。总之,块缓存层对上提供 ``get_block_cache`` 接口来屏蔽掉相关细节,从而可以上上层子模块透明的读写一个块 。
279+ 尽管在操作系统的最底层(即块设备驱动程序)已经有了对块设备的读写能力,但从编程方便/正确性和读写性能的角度来看,仅有块读写这么基础的底层接口是不足以实现高效的文件系统 。比如,某应用将一个块的内容读到内存缓冲区,对缓冲区进行修改,并尚未写回块设备时,如果另外一个应用再次将该块的内容读到另一个缓冲区,而不是使用已有的缓冲区,这将会造成数据不一致问题。此外还有可能增加很多不必要的块读写次数,大幅降低文件系统的性能。因此,通过程序自动而非程序员手动地对块缓冲区进行统一管理也就很必要了 ,该机制被我们抽象为 easy-fs 自底向上的第二层,即块缓存层。在 ``easy-fs/src/block_cache.rs`` 中, ``BlockCache`` 代表一个被我们管理起来的块缓冲区,它包含块数据内容以及块的编号等信息。当它被创建的时候,将触发一次 ``read_block`` 将数据从块设备读到它的缓冲区中。接下来只要它驻留在内存中,便可保证对于同一个块的所有操作都会直接在它的缓冲区中进行而无需额外的 ``read_block`` 。块缓存管理器 ``BlockManager`` 在内存中管理有限个 ``BlockCache`` 并实现了类似 FIFO 的缓存替换算法,当一个块缓存被换出的时候视情况可能调用 ``write_block`` 将缓冲区数据写回块设备。总之,块缓存层对上提供 ``get_block_cache`` 接口来屏蔽掉相关细节,从而可以向上层子模块提供透明读写数据块的服务 。
280280
281281有了块缓存,我们就可以在内存中方便地处理easyfs文件系统在磁盘上的各种数据了,这就是第三层文件系统的磁盘数据结构。easyfs文件系统中的所有需要持久保存的数据都会放到磁盘上,这包括了管理这个文件系统的 **超级块 (Super Block) **,管理空闲磁盘块的 **索引节点位图区 ** 和 **数据块位图区 ** ,以及管理文件的 **索引节点区 ** 和 放置文件数据的 **数据块区 ** 组成。
282282
@@ -296,15 +296,15 @@ easyfs文件系统中管理这些磁盘数据的控制逻辑主要集中在 **
296296 - Inode.read_at:根据inode找到文件数据所在的磁盘数据块,并读到内存中
297297 - Inode.write_at:根据inode找到文件数据所在的磁盘数据块,把内存中数据写入到磁盘数据块中
298298
299- 上述五层就构成了easyfs文件系统的整个内容。我们可以把easyfs文件系统看成是一个库,被应用程序调用。而 ``easy-fs-fuse `` 这个应用就通过调用easyfs文件系统库中各种函数,并作用在用Linux上的文件模拟的一个虚拟块设备,就可以在这个虚拟块设备上创建了一个easyfs文件系统,并进行各种文件操作 。
299+ 上述五层就构成了easyfs文件系统的整个内容。我们可以把easyfs文件系统看成是一个库,被应用程序调用。而 ``easy-fs-fuse `` 这个应用就通过调用easyfs文件系统库中各种函数,并作用在用Linux上的文件模拟的一个虚拟块设备,就可以在这个虚拟块设备上进行各种文件操作和文件系统操作,从而创建一个easyfs文件系统 。
300300
301301**第三步:把easyfs文件系统加入内核中 **
302302
303303这还需要做两件事情,第一件是在Qemu模拟的 ``virtio `` 块设备上实现块设备驱动程序 ``os/src/drivers/block/virtio_blk.rs `` 。由于我们可以直接使用 ``virtio-drivers `` crate中的块设备驱动,所以只要提供这个块设备驱动所需要的内存申请与释放以及虚实地址转换的4个函数就可以了。而我们之前操作系统中的虚存管理实现中,已经有这些函数,这使得块设备驱动程序很简单,且具体实现细节都被 ``virtio-drivers `` crate封装好了。当然,我们也可把easfys文件系统烧写到K210开发板的存储卡中。
304304
305305第二件事情是把文件访问相关的系统调用与easyfs文件系统连接起来。在easfs文件系统中是没有进程的概念的。而进程是程序运行过程中访问资源的管理实体,而之前的进程没有管理文件这种资源。
306306为此我们需要扩展进程的管理范围,把文件也纳入到进程的管理之中。
307- 由于我们希望多个进程都能访问文件,这意味着文件有着共享的天然属性,这样自然就有了``open/close/read/write``这样的系统调用,便于进程访问文件 。
307+ 由于我们希望多个进程都能访问文件,这意味着文件有着共享的天然属性,这样自然就有了``open/close/read/write``这样的系统调用,便于进程通过互斥或共享方式访问文件 。
308308
309309内核中的进程看到的文件应该是一个便于访问的Inode,这就要对 ``easy-fs `` crate 提供的 ``Inode `` 结构进一步封装,形成 ``OSInode `` 结构,以表示进程中一个打开的常规文件。文件的抽象 Trait ``File `` 声明在 ``os/src/fs/mod.rs `` 中,它提供了 ``read/write `` 两个接口,可以将数据写入应用缓冲区抽象 ``UserBuffer `` ,或者从应用缓冲区读取数据。应用缓冲区抽象类型 ``UserBuffer `` 来自 ``os/src/mm/page_table.rs `` 中,它将 ``translated_byte_buffer `` 得到的 ``Vec<&'static mut [u8]> `` 进一步包装,不仅保留了原有的分段读写能力,还可以将其转化为一个迭代器逐字节进行读写。
310310
0 commit comments