本章开始介绍磁盘相关的特性以及磁盘文件系统fat16,然后编写相关代码支持读写磁盘上fat16分区中的文件,从而最终实现shell能够从磁盘中加载可执行程序运行,并且应用程序能够进行文件的读写操作
1、磁盘特性简介
磁盘使用模式:
只使用LBA模式对磁盘进行读写,而不是难于理解地的CHS模式。在LBA操作模式下,磁盘就是一块块连续的、相同大小的扇区集合。
2、识别系统已有的磁盘
在一个计算机系统中,最多支持连接4块硬盘(可能有的能连接更多,但这点我们不考虑),其组织结构如下:
系统中分两条总线,分别为Primary(主)和Secondary(次),每条总线上可连接两块硬盘Primary(主)Drive和Slave(从)。每条总线上共用相同的IO端口进行读写和相关控制
为简化起见,本章仅支持Prmary Bus上的硬盘,即最多支持两块硬盘,因此其IO端口的范围为0x1F0-0x1F7,控制端口为0x3F5,连接8259上的IRQ14号上,因此在整个系统中的中断序号为0x20(8259中断起始序号) + 14 = 0x2E。
磁盘检测
可以通过识别命令来检测硬盘的存在,以及硬盘相关的信息、
具体流程如下:
返回的256个16位的数据内容:第100 - 103 个数据,共64位:保存了该磁盘总的扇区数量
-
读取状态寄存器(0x1F7):如果值为0,则表明该硬盘不存在。如果其它值,等待DRQ置位或者ERR置位
-
如果ERR清令状态,从0x1F0读取256个16位的数据,其中保存了有关该磁盘相关的信息。
3、解析磁盘分区表
主要目标是解析每个磁盘中有多少个分区,以及各分区的相关信息
磁盘通常比较大,所以可能将磁盘划分为多个分区,分别用于存储不同的数据,或者将其按照不同的方式管理其上的文件数据。具体配置由MBR中的分区表来决定。
MBR中有64字节被用于放置分区表,其余的部分用来放置引导代码和引导标志。分区中表项的格式如下:
方案构造:
由于分区指的是一块磁盘区域,因此课程中将整个磁盘看作了一个超级大的分区,以便为后续对磁盘进行读写预留扩展。其编号为:sd?0。(?取值为a, b, c....)
因此,如果我们要访问某个磁盘中所有的扇区,可以使用sd?0,而如果要访问其上的某个扇区,则只需要将编号设置成从1开始的某个整数值。
3、增加磁盘打开接口
主要目标将磁盘纳入设备管理框架下。
之前建立一个设备管理框架来对系统中所有的硬件设备进行管理。即为每种类型的设备提供相应的dev_desc_t描述结构,然后注册到设备管理层。
对于磁盘也是采用这种方式:但对于磁盘进行了进一步划分,将每个分区看作一个子设备。其编号从开始。一块磁盘最多会被映射成5个子设备,对磁盘的读写操作均是面向子设备
4、实现磁盘的读取和写入
由于每条总线上的两块硬盘共用了IO端口和中断,因此考虑到多进程同时对磁盘访问时可能引发的冲突,因此需要在读写之前使用锁进行互斥。
此外,为了避免进程原地等待耗费CPU时间,还需要借助信号来等待磁盘完成操作,以便将CPU时间释放。
4、FAT16文件系统简介
FAT16文件系统的结构以及工作原理,同时还在整个文件系统中增加了该类型文件系统相关的接口支持。
FAT16文件系统采用的是不连续的存储方式:将文件拆分成多个相同的数据块,然后在磁盘分区中找到空闲簇,写入簇。
FAT16为了便于快速找到文件中簇链的链接关系,将这种关系单独放置到了FAT表中。每一个表项都对应于一个簇,表项的值代表了簇的下一簇是什么。
即要对一个文件进行读取,需要从FAT表中找到该文件中的簇,然后根据其中的簇的编号,在从磁盘中按簇读取文件的数据
FAT16文件系统将所管理的分区划分成如下几块区域
dbr区域用于存放文件系统相关配置信息
FAT16文件系统表结构包括起始地址、表数量、表大小、磁盘大小等,这个表结构用于描述FAT16文件系统的数据分布和文件系统配置,存放在dbr区域
5、挂载FAT16文件系统
挂载FAT16文件系统的基本步骤
- 打开设备以进行读取,需要调用设备管理层的代码,并传入主、次设备号以及数据
- 主设备号和次设备号的具体值取决于磁盘类型和分区信息
- 打开设备后,通过缓冲区读取分区数据,预先分配缓冲区以便后续操作。
- 读取分区最开始的扇区(第0个扇区),将其作为子设备进行处理,读取时会根据分区起始地址自动调整扇区号
挂载过程中通过回调函数指针进程处理流程,打开设备,读取DBR数据进行文件系统解析
读取DBR数据对于识别文件系统类型、扇区大小等关键参数至关重要
6、遍历目录命令ls实现
FAT16文件系统将分区最顶层的文件和目录的相关信息以表格的形式保存在根目录
目录项的读取与缓存
- 读取目录项时,首先判断索引的有效性。
- 计算索引对应的扇区号码,读取扇区内容。
- 使用内部缓存区存储扇区内容,避免重复读取。
- 引入current sector变量记录之前读取的扇区,实现缓存效果。
遍历目录命令ls的实现、
- 从根目录区读取目录内容,包括索引和目录项。
- 通过索引找到对应的扇区,读取并解析出目录内容。
- 使用缓存技术优化磁盘读取,减少磁盘访问次数。
- 计算索引对应的扇区号码,实现目录内容的遍历。
文件名称的获取与转换
- 文件名称可能占用两个目录项,需特别处理。
- 文件名存储格式为大写字母、空格和扩展名,需转换。
- 通过指针和缓冲区,实现文件名称的正确读取和转换。
- 处理特殊情况,如扩展名后的点在文件名中的位置。
7、文件查看命令less实现
由于文件有时候比较大,需要使用多个簇构成簇链来保存。在读取文件时,只需要找到第一簇进行读取。在读取数据的时候,要考虑到读取的位置是否读取的是整簇,如果不是整簇,需要先进性内部缓存,然后在从缓存中读出相应的数据写到用户空间中
显示部分文件内容:只需要读取文件的部分内容,读取指定的簇即可
显示全部文件内容:只需要做到遍历整个簇链表即可
在遍历过程中注意FAT表的使用,其关键原理是FAT表现保存了簇间的链接关系,每个表项保存了下一个有效的簇的编号。
8、文件复制命令cp的实现
需要完成三步操作:新建文件、拷贝数据
1)要新建一个文件,只需要在根目录区新建目录项,填入文件相关的一些信息即可
2)完成文件的创建之后,就可以进行数据的写入。具体时在读取源文件的数据时,边读取边创建簇链
删除文件:
如果要将一个文件删除,则需要将上述创建的目录项删除。当然与此同时,还需要文件本身占用的数据存储簇链中所有的簇给释放掉。
9、通过文件系统加载shell
10、让shell加载应用程序
解析命令行:Shell首先解析用户输入的命令行,识别出要执行的命令名和可能的参数。
区分内部命令和外部命令:
- Shell内部维护了一个命令列表,用于识别哪些命令是Shell内置的(如
cd
、exit
等),这些命令直接由Shell解释执行。 - 如果命令不在内置命令列表中,Shell会认定这是一个外部命令,即需要加载磁盘上的应用程序来执行。
搜索可执行文件:
- 对于外部命令,Shell会在预设的环境变量(如
PATH
)指定的目录中搜索对应的可执行文件。 PATH
环境变量包含了多个目录路径,Shell会按照这些路径的顺序逐一搜索可执行文件。
加载并执行:
- 一旦找到可执行文件,Shell会使用系统调用(如
fork()
和exec()
)来创建一个新的进程,并在该进程中加载并执行该应用程序。 fork()
用于创建一个新的进程,它是当前进程的副本。exec()
用于在当前进程中加载新的程序,替换掉当前进程的映像和数据,但进程ID保持不变。
处理执行结果:
- 应用程序执行完毕后,Shell会接收其退出状态码,并根据需要进行处理(如打印错误信息)。