文件系统
让操作磁盘变得足够简单!
怎么让一个普通人即使不需要学习磁盘知识也可以使用磁盘呢?
答案是:抽象。
我们已经多次强调,操作系统就是一个魔术师,其提供给用户的就是各种幻象:抽象。进程抽象的是什么东西呢?就是CPU,虚拟内存抽象的是内存。对于磁盘来说,操作系统提供给用户的帮助就是在磁盘外面包裹一层容易使用的抽象,用户直接与这层抽象打交道,而无须了解磁盘的技术细节。
操作系统为磁盘提供的抽象就是:文件及文件系统,或者说,文件系统就是磁盘的抽象。
就像栈、堆、树一样,
文件系统
文件系统是操作系统提供的另一个抽象。它是一个子虚乌有的东西,看不见、摸不着,但却可以使用的介于磁盘与用户之间的界面,如图16-2所示。
就像一张逻辑图,向用户展现出友好的具有现实社会特点的交互, 而其背后则是一堆数据与机制的堆叠。
文件系统令磁盘变得容易使用,从而将用户从数据存放的细节中解放出来:用户不需要知道内容存放在什么地方,也不需要知道如何存放,更不需要知道磁盘到底是如何工作的。因此,文件系统就是磁盘的一个抽象。当然文件系统不一定在磁盘上,也可以在光盘上。但由于绝大部分文件系统是基于磁盘的,本章讨论的文件系统主要针对磁盘。
简单地说,文件系统将其接触的磁盘物理特性转换为用户看到的路径名和文件名。用户对磁盘进行访问只需要给出文件名和路径名即可,而无须知道磁柱、磁道、扇面、数据块等信息。
文件系统的主要特性就是存储大量的信息,多个进程可以同时访问一个文件,进程结束也不会影响文件的持续存在。
16.3 文件系统的目标
与内存管理系统类似,文件系统也需要达到两个目的:地址独立和地址保护。
地址独立就是一个文件在产生的时候无须担心其存放的磁盘地址,即文件数据的产生与文件将来存放的磁盘地址相互独立。
而地址保护则需要对文件的访问进行一定的限制,即不是任何人都可以访问任何文件的。
注意,这里的保护与内存地址保护是有区别的。内存管理下地址保护指的是一个进程不能访问另一个进程空间,而这里的保护不是一个文件不能访问另一个文件空间,而是一个文件的访问是有限制的。
从用户角度看文件系统
我们从用户的角度来讨论文件系统,或者说用户看到的文件系统是什么形式。从用户角度分析,文件的地址独立和地址保护是如何实现的。
从用户的角度来看,对用户来说,他关心的是这个文件是什么东西:比如说什么是一个文件和对文件可以进行何种操作,即用户关心的是“怎么用”;
而对操作系统设计人员来说,他们关心的则是文件是如何构造的,如何跟踪磁盘上的自由空间,具体的磁盘结构,比如一个逻辑块有多少个扇面,即设计人员关心的是“怎么实现”。
- 文件是如何命名的?
通常来说,我们给出一个符号名,也就是文件名以便于访问一个文件。
当用户给出一个文件名, 操作系统就可以根据这个名词找到对应文件的内容。
具体来说,就是找到该文件存放在磁盘上的所有数据块。 操作系统如何在磁盘上找到所有这些数据块呢?
即,将该文件名翻译成其数据块在磁盘上存放的地址。
由于磁盘的寻址单位是扇面,而这些磁盘地址实际上就是一个个的扇面号。由于一个文件可能占用多个扇面,因此文件在磁盘上的地址通常有多个。根据文件在磁盘上的组织方式不同,这种从名字到磁盘地址的翻译将有所不同。
命名规则:
命名规则则因文件系统的不同而不同。一个文件名通常有2~8个字符,但也可以长达255个符号。
地址独立的实现机制:文件夹
我们可以对文件进行读写等操作,那么给一个文件名,操作系统是怎么知道从什么地方读取文件的内容呢?或者说,文件在磁盘上的什么位置,操作系统是如何知道的呢?
这当然需要一个数据结构来记录每个文件在磁盘上的地址, 这个数据结构就是文件夹。
文件夹也称为目录夹(folder),它保存的不是用户数据,而是关于文件及文件系统的信息。
简单地说,文件夹的角色就是跟踪文件,里面存放的是从文件到文件在磁盘的地址的映射,
即“文件名→文件在磁盘上的地址”。
文件夹对于文件来说,就相当于动态地址翻译对于虚拟地址的作用,即从虚拟地址到实际地址的一种翻译机制。
这样,当给予一个文件名时,我们从文件夹里可以找到该文件所在的位置,从而发出正确的读取命令。
那么文件在磁盘上的地址是什么呢?这就要看文件在磁盘上是如何存放了。如果文件在磁盘上是连续存放的,即一个文件占用一片连续的磁盘空间,则文件在磁盘上的地址就是该文件的第一块数据块所在的磁盘扇面号。
起始位置就是文件在磁盘的地址。
当然,文件的存放不只有连续存放一种模式,因此文件在磁盘上的地址还有别的形式,我们稍后解释。但总的一点就是,文件夹应该提供一种找出一个文件所有磁盘块的方法。
既然文件夹存放的是文件名到磁盘地址的映射,那么自然地我们可以以一个简单数组来实现
。
这个数组是以文本文件存放的,你可以自己写一个程序打开这个文件就可以看到里面的内容。而读取文件夹的内容就变成读取这个简单数组的内容。而这正是shell utility “ls
” (UNIX、Linux、Windows Power Shell)
和"dir
"(Windows)所做的事情。
ls 和 dir
这里很重要的一点是文件夹本身也是文件。因此,我们可以像对待文件一样对待文件夹:使用同样的存储结构来存放文件夹的数据;文件夹里面的每个记录本身又可以是文件夹
16.6.1 文件夹结构
由于文件夹里又可以有文件夹,这样就形成了一个层次结构。这个层次结构的顶端就是根文件夹,也称为根目录,如图16-7所示
根目录是一个文件系统的总起点,它在操作系统启动的时候加载到内存
。
该文件系统里面的所有文件都可以找出来。由于根目录是整个文件系统的源点,如果根目录损坏,则整个文件系统都无法访问,也就说文件系统已经崩溃。(当然,我们可以通过文件恢复和修复等手段来获得对部分文件的访问能力,但这不是本书要探讨的问题。)
16.7 文件系统调用
从用户角度来看,操作系统提供的文件系统界面就是文件系统调用。用户可以在程序中通过这些系统调用对文件系统进行读写操作。
这些系统调用包括打开文件、关闭文件、读文件内容、写文件内容等。
例如,图16-8给出的就是使用文件系统调用实现文件拷贝的简单程序。
上述文件拷贝程序的工作原理很简单:
- 打开源文件和目标文件
- 循环往复地将源文件的内容读到一个缓冲区,然后将缓冲区的内容写到输出目标文件里
- 当拷贝结束后,关闭源文件和目标文件。
上述程序中使用的文件系统调用包括fin.open、fout.open、fout<<、fin.get等。
这里需要注意的是,输入文件的打开模式为只读,而输出文件的打开模式是只有创立这个文件的人才能读写,其他人均无访问权限(输出模式0700代表的就是这个意思)。
上述程序的问题:
- 在效率上却不太令人满意。这是因为该程序的系统调用次数太多,而
系统调用需要陷入到内核
。 - 每次系统调用都需要进行磁盘操作,而磁盘操作的效率远远低于内存操作的效率。
解决这个问题的办法是可以将缓冲区变大,从而降低系统调用和磁盘访问的次数。但缓冲区越大,需要的内存就越大,将造成程序的可执行性下降。而且,就算缓冲区很大,文件还可以更大,还是不能从根本上解决效率低的问题。
如何解决效率低下的问题?
不使用系统调用,而是采用内存映射的文件访问。
16.8 内存映射的文件访问
解决读写文件效率低的中心思想就是把磁盘访问变成内存访问
。实现这种访问转变的手段就是内存映射的文件访问。
内存映射的文件访问原理很简单:
把需要访问的文件映射到一个进程的虚拟地址内。这样,访问该虚拟地址就相当于访问文件。这样文件访问就变成内存访问。
有些抽象。
例如,假定我们将一个32KB大小的test文件映射一个进程的虚地址1024KB处。那么读写1024KB的地址就是读写test这个文件的第一个字节。而当该进程中止的时候,与文件对应的这个内存内容将被冲洗(flush)到磁盘上,如图16-9所示。
这样做的好处就是磁盘访问变成了内存访问,效率自然提高。例如,还是我们前面的文件拷贝,这次不用文件系统调用来实现,而是使用内存映射的文件访问来实现。例如,我们可以将文件a和b均映射到同一个进程的不同虚拟地址上。这样读a文件对应的虚地址将把a的内容从磁盘上拷贝到内存,再把内容写到b对应的虚地址上即可。
不过需要提醒的是,内存映射的文件访问不是为了文件拷贝而提出的,而是为了文件的共享。具体来说,我们可以将同一个文件映射到两个或多个进程的虚地址空间。这样每个进程对相应的虚地址空间进行访问时就是对同一个文件进行访问
原理链接:https://blog.csdn.net/whoamiyang/article/details/53365385