操作系统开发问题
这段时间没事在自己写操作系统,其中遇到了很多问题,想和大家分享一下我研究的一点成果
以及讨论一下技术方案。
在我理解的操作系统,主要由 Bootloader + Kernel + Shell 所组成,目的是完成一个管理
计算机资源并提供相关接口开发应用程序的平台基础。
Bootloader:可以分为 Boot 程序和 Loader 两个程序所组成,也可以直接是一个程序完成,
但主要看 Kernel 架构和文件系统等选型决定是分两个程序还是一个程序完成工作。
其实 Bootloader 主要是计算机启动时自动加载到内存执行的第一个程序,也被称为引导程序,
它是运行在 CPU 的实时模式下的 16 位程序,而系统启动时只会加载 512 个字节,即一个扇区
的数据到内存执行,这也就限制了 Boot 程序或这说 Bootloader 程序编译后的大小不能超过
512 个字节,如果文件系统定义得不那么复杂,而且内核加载过程不需要特别初始化的话,这个程序
完全可以用 Bootloader 的方式一次加载内核(比如内核程序直接有汇编开发)。
但如果想实现 C/C++ 语言来开发内核,而编译器又是现有系统的编译器,这个 Bootloader 就得
分两个步骤来完成,先是 Boot 程序,功能是先加载 Loader 程序,然后由 Loader 程序初始化
好环境再加载 Kernel。
Kernel:这个所谓的内核,在我的理解就是提供了一堆预先写好的基础函数放到内存中共其他应用
程序调用。比如提供 OS_ReadFile、OS_LoadAPI 等函数,然后应用程序启动时可以通过特定的内存
地址找到这些函数的内核地址,从而用跳转指令调用这些函数。这些一系列的函数累加起来,实现
了系统所谓的内存管理、进程管理、线程管理、设备管理等等功能。然后 Kernel 的初始化及载入
过程完成后就可以把执行权移交给 Shell 与用户进行交互了。
Shell:在我的理解,Shell 就是人机交互的管理程序,如 DOS 下的命令行形式的人机交互方式,
或者是 Windows 下图形模式的人机交互方式,理论上说,这个 Shell 是系统执行的第一个“应用程序”,
理论上 Shell 里的过程基本上全是调用 Kernel 所提供的函数完成的。
到现在为止,对于写“操作系统”,我越来月不清晰,因为有以下几个顾虑。
1、开发环境
如编译器是自己写好还是用现成的好,如果用现成的,免不了要根据别的系统走开发路线,而且
也未必是能实现我想用 BASIC 开发系统的想法。但自己写编译器难度又较大。
2、内核的定制
如果用高级语言开发内核,还是与编译器有关,包括应用程序的执行问题,这个内核还是与编译器
息息相关,如果不考虑高级语言,用汇编来写,灵活性比较大,但如果要写大,代码管理也很叫人
头痛。而且最终的应用程序管理部分也会卡在可执行程序的格式定义上,因为将来的应用程序不可能
也全由汇编开发,这样就没什么价值了。
3、是否采用现有系统的模型
比如 Windows、Linux、MacOS、DOS、uCos、Menuet OS 等,都有自己的特点,自己定制方式是比较随意,
但相对来说所有东西都要自己做,就比如如果不用现有的文件系统格式,自己弄一个,可能会更方面,
但即使格式化和复制文件这种操作才最初的时候都要自己写一个程序去做这种事,然后才能调试效果。
但如果运用的现有系统架构,那今后的开发路线就是个模仿的过程,基本上还不如直接用那些系统来得
有意义。
目前在开发操作系统的时候,我采用了软盘上用的 FAT12 文件系统进行系统设计,主要是为了在 Windows
下方便管理文件。但发现这样很耗代码,因为 FAT12 文件系统大致分为4个部分:
1、BOOT 区域,就是引导用的第一个扇区,也就是所谓的 MRB 表。如果要保持标准的 FAT12 文件系统,
必须按照它的固有格式开发 Boot 程序,如:开始是3个字节的跳转指令,然后跟着 BPB 表信息,然后
才开始 Boot 程序的代码,最后就是 0xAA55 的引导结束符号,所以,这个 BOOT 程序实际上最多只有
448 个字节。
2、FAT 表区,这个 FAT 其实是 File Allocation Table 的缩写,主要是记录文件数据的族链信息,
只有在文件数据内容大于一个族(512字节)才会具体指向数据的逻辑族地址,如果文件数据内
容在512个字节内,是不会指向具体的数据所在逻辑扇区地址的。而且系统通常会备份一个FAT表,
这样即使有一个 FAT 表因为磁盘坏道不能正常读取文件数据起始地址,也有另外一个表可以顶上。
3、FDT 表区,这个 FDT 其实是 File Directory Table 的缩写,主要存放文件名、目录名或卷标等
信息,包括名称、属性、相关日期时间,数据起始扇区号、文件大小等,当文件大小小于或等于
512个字节时,其实没必要访问 FAT 表去读文件数据,因为 FDT 表已经有了数据起始扇区号的信息,
因为只有一个扇区数据,直接读那个扇区就好了。当文件数据超出一个扇区时,就需要依赖 FAT
表进行数据族链的查找了,只要在 FAT 表中搜索一个链头与 FDT 数据起始扇区号相同的就可以
按照循序访问到文件数据分布在那几个扇区号上。
4、数据区,也就是存储文件具体数据的地方,通常文件数据会以扇区为单位存储。如果文件较大,
可能产生一个文件存放数据分布在不连续的扇区上,如一个5个扇区数据的文件,他可能存储在
0x003 0x004 0x005 0x01E 0x01F 的地方,这时候就需要 FAT 的族链来找到这些数据的分布扇区
情况。
看到这里,可以想象,如果要根据这种方式找到指定文件,并把所有数据读出来需要的代码量肯定不小。
而且同时还要将运行模式初始化到 32 位系统的保护模式,并执行现型系统的可执行文件格式,那就更难
了。所以我采用 Boot 和 Loader 分开的模式,先加载稍微大点的 Loader 程序,然后再用 Loader 程序
处理这些更复杂的操作并加载内核。
但我这段时间还看了一些别的 OS 代码,发现, Kernel 部分如果不用现有系统编译器的程序编写,如果
直接用汇编写,整个初始化过程可以更简单,那个 Loader 程序其实就可以直接写成 Kernel 了。
我的想法是用 Basic 开发这个系统,本来的方案是这样,先写一个 BASIC 编译器,主要思路是写一堆
的汇编函数,模拟 Basic 的语法函数过程,然后分析 Basic 语法,把配合的语法用汇编替代,这样实现
Basic to Asm 的功能,然后再用汇编编译器编译程序。
这样即使使用的是 Basic 语言,其实出来的还是汇编的效果。不过因为全是调用封装的汇编函数,效率
肯定与直接用汇编根据情况编写代码要低一点。但起码实现了 Basic 开发系统的可能性。
但后来在运用 NASM 编译器时,发现 NASM 缺少些指令,如 cmps 等等,MASM 就没这种问题,但也存在
别的问题,所以我的代码该按照 NASM 的标准翻译还是 MASM 的标准翻译又成为了一个问题。后来就自己
去看了下 CPU 指令集相关的资料,发现历年来系统的发展、各种 CPU 指令系统的升级累加,从这方面入
手也不容易,比如 Inter 的 x86、Pentium 、MMX、CISC指令集等等等等,除了多还要考虑程序的兼容性
等等。感觉自己按照 CPU 指令集编写 basic 编译器写操作系统的想法越来越远。
相对来说,觉得用汇编写内核和应用程序要比用这种高级语言简单得多,但如果这样做下去,这个系统也
没什么价值。所以想大家给点方案,怎么去设计这个系统比较好。
如:用什么编译器?结构如何?文件系统有没有更好的方案?……