一、生磁盘的使用
1.1 认识磁盘
概念:
磁头与磁臂相连,磁臂能将磁头作为一个整体而一起移动。磁盘片的表面被逻辑地划分成圆形磁道,磁道再进一步划分成扇区,一个扇区大小通常为 512B。位于同一磁臂位置地磁道集合形成了柱面。
磁盘的访问时间:
1.2 最直接的直接使用磁盘
当使用磁盘时,先将磁头移动到指定的柱面(cycle),然后旋转盘片到指定的扇区(segment),最后给磁头(head)通电,旋转盘面读取数据。所以,要使用磁盘,就是找到对应的 CHS。
1.3 通过盘块号读写磁盘(第一层抽象)
直接由用户计算 CHS 过于麻烦,所以提出了通过盘块号计算出 CHS 来访问磁盘。利用统一的盘块号来将磁盘抽象,用户只需要提供一个盘块号,操作系统就能计算出 CHS。计算公式如下:Block = C * (Headers * Sectors) + H * Sectors + S,通过这个公式可以反推回 CHS。
同时,由磁盘的访问时间公式,可以很容易地明白,相邻的块要尽量放在相同柱面的相邻扇区,如果一个磁道放不下,就要放到下一个盘面的相同磁道。
盘块:连续的几个扇区,如果一次能多读几个扇区,那么读写速度就会提升。(节省了寻道和旋转次数)
1.4 多个进程通过队列使用磁盘(第二层抽象)
当多个进程要访问磁盘时,因为它们的访问目标可能不在相同的磁道,所以要为多个磁盘请求设计调度算法。
FCFS 调度:先到先服务,最公平,但是因为磁头在不停地来回摇摆,所以性能最差,如下图,磁头共移动了 640 磁道。
**SSTF(最短寻道时间)**调度:先处理靠近当前磁头位置的请求,缺点是远处的请求可能会调度不到,产生饥饿。
SCAN 调度(电梯算法):SSTF + 中途不折回,缺点是中间的请求被处理的概率大,而两边的小。
C-SCAN 调度:SCAN + 直接移动到另一端,这样两端的请求就都能很快处理。
LOOK 调度:上述的两种 SCAN 算法都存在一个问题就是每次都将磁头移动到最边缘,但其实可能边缘并没有请求,所以提出了 LOOK 算法,每次只将磁头移动到有请求的最边缘。
二、从生磁盘到文件(第三层抽象)
在 1.3 中提到了盘块号,但是盘块号对于用户来说依然难以使用,所以就提出了文件的概念。
用户眼里的文件本质上就是字符序列(流),而这些字符序列是存储在磁盘的块上的,所以从生磁盘到文件就是要建立字符序列与磁盘块的映射关系。
建立映射关系需要知道该文件在磁盘中的起始块以及块数,我们将这些信息保存在每个文件对应的 FCB 中。
我们可以采用如下的几种结构来实现文件:
连续结构实现文件:将文件所占用的盘块连续存储,访问某一个位置的字符方式就是:
block = index / block_size + base,存在的问题与我们平时使用的数组一样,不利于增删。
链式结构实现文件:如同链表一样,每个块都记录了下一个块的位置,存在的问题是顺序访问太慢
索引结构实现文件:连续和链式分配的折衷,在 FCB 中记录文件索引存在的块,这个块读取索引来访问实际文件。
多级索引:在实际系统中,使用的是多级索引。因为如果一个文件太大,一个块将存不下它的全部索引。
对于不同的文件,我们可以使用不同的结构来实现,比如说像词典这种文件,它经常被访问而不被修改,所以我们就可以使用顺序结构来存储。
三、目录与文件系统(第四层抽象)
文件系统(熟磁盘)就是操作系统负责管理和存储文件信息的软件,管理包括增删改查,存储就是对文件的存储方式,如目录,我们这里主要讨论存储。
文件系统的存储方式就是目录,目录是通过树结构来存储的。
所以,要实现文件系统的存储,关键就是要实现目录。目录的本质也是文件,它里面的内容就是它里面的文件名和对应的 inode。
有了根节点才能够访问一棵树,所以根节点的 inode 在磁盘中被固化在了固定的位置,即 0 号位置。
四、修改一个文件的流程
明白了上述概念之后,倒推回去就能够理解操作系统修改文件的流程了(比如修改 /xx/text.c 的第 202 到 212 字节):
1、解析目录找到 /,读取 / 的内容找到 xx,再找到 test.c 的 inode
2、根据找到的 inode 和 202 字节找到对应的盘块号,比如 789(通过索引)
3、把 789 加入到电梯队列
4、从队列中取出 789,算出 cycle,head,sector
5、写入修改信息