计算机自制操作系统(二八):支持命令行操作
在本专栏《计算机自制操作系统(八):仿生DOS操作系统源代码》中,我曾经用纯汇编程序自行设计和编写了一个简易版的DOS操作系统。它支持基本的命令行:mkdir,dir,format,cls,cd,del...等对文件和目录进行操作。它的核心基础是在是实模式下,利用BIOS的 INT 13H中断读写磁盘功能来进行实现简易的磁盘文件操作。当然,文件和目录的组织方式需要自己设计(文件系统)。
我们现在进入了保护模式,要实现磁盘的基本命令行操作反而更加困难了,因为BIOS的中断调用无法再使用。由于操作系统在后面要每进一步,文件的作用越来越重要,如要显示文本,要显示图片,要播放视频,都需要建立在“文件”的基础上。所以,本章我们的操作系统就来实现“文件”的读取功能。
一、文件系统
我们的操作系统目前还没有文件系统,只有先借用别人的文件系统标准---微软FAT。事实上,我们的操作系统启动软盘镜像文件就是利用的FAT 12标准,最早在《计算机自制操作系统(一):最小操作系统》中,介绍MBR的时候就所涉及到了。在本章,我们将重点研究它。
FAT12文件系统格式如下:
FAT12文件系统由引导区,FAT表,根目录项表和文件数据区组成。
(一) 引导程序(扇区):就是MBR,我们已经学习过了。
(二) FAT表 :FAT表是FAT12的数据组织核心,它的主要特性是:
- FAT1和FAT2是相互备份的关系 ,数据内容完全一致
- FAT表是一个关系图,记录了文件数据的先后关系
- 每一个FAT表项占用12比特。这正是FAT12的由来,故你可知FAT16和FAT32的意思了。
- FAT表的前2个表项规定不使用
(三) 目录文件项:每一个目录项代表根目录中的一个文件索引,格式为:
这个区域起始于第19扇区,相对偏移地址是0x2600。每个文件索引中,比较重要的一个数据是DIR_FstClus,它代表的是该文件内容的起始扇区,因此它代表了文件内容的定位信息。另外,它还有一个更重要的作用,就是用它来定位FAT表中该文件的表项数据起始位置。
(四)文件数据区:这是真正存放每个文件数据的地方,但是需要根据FAT表中的内容来索引映射才能找到相应文件的数据。
由于它特殊的存储方式,因此每个文件的内容数据逻辑上是以链表的形式组织的:
该区域开始于第33扇区,相对偏移地址是0x4200。这个就是之前反复提到的,我们的操作系统内核代码开始的地址。
二、文件名显示---dir
我们以在软盘镜像文件haribote.img中放入haribote.sys,ip110.nas和make.bat这3个文件为例,来说明FAT12的文件组织方式。
首先来看应该如何显示软盘下根目录下的所有文件。从前面可以看出,由于所有的文件信息都是放置在第19扇区根目录区(偏移地址0x2600),打开haribote.img验证一下:
因此,我们只需要在该区域遍历出所有的文件即可,一般来讲遇到文件名开头是0x00就代表没有文件了。前面介绍了每个文件都是32字节的标准格式,因此我们需要用C程序定义如下的文件数据结构:
struct FILEINFO {
unsigned char name[8], ext[3], type;
char reserve[10];
unsigned short time, date, clustno;
unsigned int size;
};
这样,我们只需要定义一个指向该结构体的指针,就能用它来寻找到所有的文件了。
# define ADR_DISKIMG 0x8000;
struct FILEINFO *finfo = (struct FILEINFO *) (ADR_DISKIMG + 0x002600);
三、文件内容显示---type或cat
文件内容的显示就比文件名复杂多了,原因在于每个文件数据内容是存放在数据区的。文件内容显示的命令一般设计为: type(Windows)或cat(Linux) 文件名。
我们以type ip110.nas为例,说明在数据区找到目标文件的数据,需要以下几步:
(一)在目录项区域根据文件名定位索引位置。
在指针*finfo基础上遍历做文件名的匹配操作,如果匹配成功,则定位为位置x,这样就定位到要显示的文件基本信息是finfo[x]。这次ip110.nas文件显然位置是:finfo[1],haribote.sys是finfo[0]。
(二) 在目录项区域找到目标文件的起始簇(扇区)
也就是前面图中的DIR_FstClus,在我们的数据结构中是:clustno。那么我们就可以根据上一步的操作找到目标文件的起始扇区位置:finfo[1].clustno=0x003A,这个0x003A表示目标文件的第1簇(扇区)编号(即起始编号)。
(三) 在FAT表中找到目标文件的剩余簇(扇区)
那么目标文件在FAT表中的表项数据从哪里开始呢?前面说过了,还要用到clustno,也即是:0x003A。我们把目标文件ip110.nas的起始簇(扇区)的编号记为:clustno_0=0x003A。
1.找下一个簇(扇区)的编号
我们在FAT表中,找到相对偏移地址0x003A=58处的数据是0x03B:
这个0x03B代表的含义是目标文件ip110.nas下一簇(扇区)的编号clustno,也即第2簇(扇区)编号clustno。记为:clustno_1=0x03B。
2.重复寻找下一个簇(扇区)的编号
在得到clustno_1之后,还需要重复上述寻找过程下一簇(扇区)的编号clustno:在FAT表中找到相对偏移地址0x003B=59处的数据是0x03C。记为:clustno_2=0x03C。
3.文件结束位置
这样一直重复寻找下去......如果遇到表项值是0xFF8-0xFFF,就代表该文件到末尾了。
总结一下,目标文件ip110.nas的所有簇(扇区)的编号clustno已经全部找出:从clustno_0一直到clustno_5,对应的值分别是:0x03A---x003F。
可看出,寻找目标文件内容的过程就是一个链表接力的过程。碰巧上面的例子是物理位置上连续存放的,对于不是连续存放的例子,就能体会到链表的作用。
但在这一步中,还有一个隐藏的问题:就是上面每个表项12BIT一组对应的簇(扇区)数据不是明文的,因为微软公司采用了一种压缩算法,需要我们“解压缩”才行,上面的数据是已经解压缩了的情况。关于如何解压,不详细再说了。它没有解压的数据,还是传统的如下格式,但是没有人看得懂它的含义:
(四) 在数据区找到目标文件的内容
我们已经找到了目标文件内容的所有簇(扇区)编号:clustno,但是要真正定位它在数据区中的位置,还需要用如下的公式计算:
这个很好理解,就是把文件内容的相对位置最终翻译成在整个软盘的绝对位置。那为什么不是数据区起始地址0x4200,而是0x3e00呢?前面说了,由于FAT表项的前2项0和1是不可以用的,因此第一个文件最低表项值是2,显然0x4200-2*512=0x3e00。
(五) 在数据区读出目标文件的内容
当找到目标文件的所有簇(扇区)地址之后,就可以用如下程序从数据区来读出文件内容了:
void readdata(int addr);
unsigned short *nextp;
nextp=(unsigned short *) 0x003A;
while (*nextp!=0xfff) /*一直读到文件所有的簇(扇区)结束*/
{
clustno=*nextp;
addr=clustno*512+0x3e00;
readdata(addr); /*读出一个簇(扇区)的数据*/
nextp=(unsigned short *) (*nextp); /*取出文件的下一个簇(扇区)编号*/
}
四、程序演示
具备以上基础之后,把方法变成程序,就可以实现文件的显示了。
先用工具WinImage把一个测试文件readme.txt,装进我们的操作系统软盘镜像文件:JiangOS.img。
可以看到,在这个软盘中目前只有2个文件:操作系统内核kernel和readme.txt。
现在,用该软盘来启动计算机,看看我们的操作系统是否能正常显示出该软盘中的文件。
可以看到,我们输入dir命令成功显示了软盘下的所有文件,而且文件大小也是对的。
我们再输入type命令,看能否显示readme.txt中的内容:
这样,我们的操作系统借助于微软的FAT文件系统,实现了文件的读取。跨过这一步,后面我们基于文件的操作就会相当的方便了。
五、总 结
FAT 12已经是文件系统里面最简单的结构了,但是可以看出其寻找文件的过程其实并不轻松。有很多操作系统在刚开始自举启动的时候,其实就是利用了FAT 12文件系统在搜索一个叫"kernel.bin"的文件来装载内核的。但是由于机器刚开始启动的时候,还没有进入保护模式,这个搜索过程一般只能用汇编语言来实现,加之又叠加了文件系统和C\H\S等扇区计算,所以写出的程序在识别和装载kernel.bin的时候,简直像天书一样的难懂。对于采用这种方法来写出的操作系统一些书,简直是本末倒置,读者本来想好好学习一下操作系统的基本原理,结果一开始就被一个内核文件的搜索绕得晕头转向而放弃。