上篇文章简单的介绍了PiscisOS的概况,这篇文章详细的说明整个系统的开发方法。因为操作系统涉及接近硬件底层的编程,在我的接触范围内还没找到一个很好的IDE能包办自始至终的编码和调试,所以不可避免的要自己选择一个适合自己的开发方法,包括开发语言的选取,编译(汇编)软件的选择,调试工具的选择,运行环境的选择和其他一些工具软件的选择。
首先把PiscisOS开发中使用的所有软件列一个清单:
开发平台:Windows 7
开发语言:汇编语言
汇编程序:Flat Assembler(FASM)
源码编辑:Notepad++
调试工具:Bochs 2.5
运行环境:VMware 8.1
镜像工具:WinImage
其他工具:WinHex,Total Commander
开发平台用windows,在我所了解的几种微型操作系统的实现中,还没有见到用Windows作为开发平台的,都是用Linux。开发平台的选取对整个开发方法的影响不是很大,除了后面WinImage的选择是跟开发平台有关外,其他几种工具都可以在linux上找到替代或者专门的Linux版本。下面分条详细介绍选择这些工具的原因。
1.开发语言
PiscisOS整个系统中所有的源码均采用汇编语言完成,并且应用程序也只支持汇编语言开发。为何选择了单一的汇编语言,主要有一下几个方面的因素要考虑。
首先,由于PiscisOS不基于现有的操作系统内核,所以要求最终编码生成的二进制程序纯净,不包含编译器或者汇编程序添加的用于某一指定文件格式的冗余代码。
其次,PisciOS最终架构中没有提供内存管理的模块,如果要为PisciOS开发应用程序则程序员必须负责所有的内存管理工作。
第三,操作系统的编程中对内存地址的使用十分明确,很多时候需要我们精确的把某段代码或者某个数据放在指定的内存地址中,C语言的指针可以完成,但是相对来说不直观。
第四,PiscisOS的最初的设计也没有考虑去支持现有的比较流行的可执行文件格式如ELF、PE等,准确的说是一开始就摒弃支持他们,但是今后会考虑支持ELF格式。至于PE,如果能支持的话,估计我的这个试验品系统就达到商业级别了,所以不会再考虑。
鉴于以上原因,排除了用高级语言来开发的选择,因为高级语言编译器需要内核支持内存管理,并且生成的目标程序镜像文件中会添加一些特定于某些系统使用的冗余代码比如PE头部等。所以PisciOS最终选择了采用汇编语言来实现所有的开发任务。
2.汇编工具
编写操作系统程序最头疼的问题就是很多编译器和汇编程序都会在生成的目标程序镜像文件中添加多余的冗余代码,而这些代码对于一个没有装载任何操作系统的计算机来说都是无用的,所以应该尽量避免在开发生成的文件中引入冗余代码。
目前有许多流行的汇编程序可以选择,但是大多数汇编程序仍然会带来冗余代码的问题,比如MASM,虽然可以通过后期的工具转换,但是这也带来了开发效率的问题,所以排除了使用MASM汇编程序。
还有NASM,这个应该是我见过用来写操作系统最多的一个汇编程序了,可以生成纯净的二进制文件,但是由于本人汇编起于MASM所以及其不适应AT&T的汇编语法,所以也排除了使用这个汇编成程序。
综上所述,PisciOS选择了使用Flat Assembler(FASM)来开发。Flat Assembler是一个快速高效的80x86的汇编工具,支持DOS、Windows、Linux操作系统,支持包括 8086-80486/Pentium instructions with MMX, SSE, SSE2, SSE3, and 3DNow! extensions and x86-64 (both AMD64 and EM64T) 指令集。Flat Assembler的最大优点是编译过程简单,使用一个命令就可以完成生成适用与不同系统的目标程序,所有的控制都在汇编代码中通过简单的伪指令来进行区分。关于FASM的详细信息请看这里:http://flatassembler.net
PisciOS主要利用Flat Assembler的生成纯净二进制格式的特性,生成不包含任何冗余代码的目标程序镜像文件。而且Flat Assembler的语法继承于MASM并且做了很多优化,相对的提高了开发效率和代码易读性。
3.源码编辑
由于Flat Assembler的自有代码编辑器功能较弱,所以PisciOS开发过程中采用了Notepad++程序作为源代码编辑器,结合其插件NppExec可以搭建简易高效的IDE开发环境,如图1所示:
图1 Notepad++搭建的IDE开发环境
搭建方法为设置NppExec的一个自动处理宏命令,首先打开NppExec插件的Execute…命令,出现对话框,然后设置命令,命令的格式为:
"FASM程序绝对路径" "$(FULL_CURRENT_PATH)"
比如PisciOS开发时Fasm.exe所在路径为F:\Flat Assembler\FASM.EXE,
则设置命令为"F:\Flat Assembler\FASM.EXE" "$(FULL_CURRENT_PATH)"。
然后保存该宏命令,命名为“Fasm-Build”,如图2所示:
图2 设置NppExec
然后调整NppExec的基本设置,选择No internal messages和Save all files on execute,使NppExec在执行时不输出内置消息只显示Fasm的输出,并且在执行时自动保存所有文件,如图3所示设置:
图3 NppExec基本设置
然后打开NppExec的Advance Options,设置其高级选项,添加一个菜单项在NotePad++的“宏”下拉菜单中,设置如图4所示:
图4 NppExec高级选项设置
为了实现在出现汇编错误时能准确快速的定位到源代码的错误文件和错误行,还需要设置Console输出过滤选项,打开“NppExec Output Filters…”选项卡,进行如图5所示设置:
图5 NppExec控制台输出过滤
这样当在编译过程中出现错误时,通过双击控制台输出的错误信息即可定位到错误源文件的错误行,如图6所示:
图6 错误定位
经过这些设置后,就可以进行方便快捷的开发环境了。
4.调试工具
PisciOS采用虚拟机进行调试运行,调试时采用Bochs,因为其提供了对虚拟机CPU的时序仿真,可以根据需要调整设置CPU的时钟频率,并且提供了一套功能完善的调试界面。
Bochs的常用调试命令如表1所示:
表 1 Bochs调试命令
在某物理地址设置断点 | b addr | b 0x30400 |
显示当前所有断点信息 | info break | info break |
继续执行,直到遇上断点 | c | c |
单步执行 | s | s |
单步执行(遇到函数则跳过) | n | n |
查看寄存器信息 | info cpu r fp sreg creg | info cpu r fp sreg creg |
查看堆栈 | print-stack | print-stack |
查看内存物理地址内容 | xp /nuf addr | xp /40bx 0x9013e |
查看线性地址内容 | x /nuf addr | x /40bx 0x13e |
反汇编一段内存 | u start end | u 0x30400 0x3040D |
反汇编执行的每一条指令 | trace-on | trace-on |
每执行一条指令就打印CPU信息 | trace-reg | trace-reg on |
但是Bochs唯一的缺点是其所支持设置的最高CPU频率仍然达不到真实的CPU频率,所以当遇到对CPU时钟频率要求非常严格的代码时Bochs也将会无计可施,这时候只有人工复查代码。图7为使用Bochs进行内核调试:
图7 Bochs调试环境
5.运行环境
在检验PisciOS的功能正确性,以及最终运行时仍采用虚拟机进行,这时将采用VMware来作为运行环境,因为VMware与真实的物理机器几乎没有任何差别,所以可以很好的检验最终的操作系统是否达到了要求。图8为用VMware运行PisciOS:
图8 VMware运行环境
6.镜像工具
在最终组织PisciOS各模块文件的时候,采用WinImage创建软盘镜像,然后把所有文件写入该软盘镜像。WinImage的使用如图9所示:
图9 WinImage制作磁盘镜像文件
7.其他工具
其他用到的工具还有WinHex和Total Commander,后者不做过多介绍,主要是为了文件管理方便,Winhex需要解释一下。
在调试的时候需要下断点,在源代码中我们可以很清楚的看到我们要在哪里下断点,但是编译之后有些代码在内存中的地址不确定,所以需要借助Winhex来计算我们下断点的线性地址。做法是,在源码中定义几个特殊字符,比如一连串的字符串“debugdebug”,然后编译生成二进制文件,然后用Winhex打开文件,搜索我们定义的字符串,找到我们要下断点的偏移地址,然后把这个偏移地址加上该模块被加载到内存中的基址,结果就是我们要下断点的线性地址(由于PiscisOS的内存分页映射是一一对应的即:物理地址0对应线性地址0,所以物理地址就等于线性地址)。然后在Bochs调试的时候就可以得心应手的下断点,即使找不到精确定位的字节地址,也可以通过反汇编命令看到加载在内存中的代码,所以仍然可以精确定位断点。INT3指令我用过,但是在bochs中执行到该指令之后无法继续执行,所以才采用了这种比较复杂的方法。
以上就是所有开发中用到的工具的选择,以及环境的搭建。开发环境搭建好以后就可以开始操作系统的开发工作了。首先按照系统的逻辑功能分模块的进行编码,请注意这里所说的模块只是逻辑上的,因为最终编译的时候只有一个编译单元,就是pkernel.asm,其他模块都是独立的.inc源文件,在编译时编译器将所有的.inc文件包含在pkernel.asm文件中,最终生成一个pkernel.bin二进制文件(实体的内核模块)。Boot sector和其他的应用程序也编译生成对应的二进制文件,最后将这些文件组织用WinImgae按照FAT12文件格式写入一个软盘镜像文件中,然后通过bochs或者vmware来加载这个软盘镜像进行调试运行。如果遇到错误代码并且通过调试定位后就可以修改对应的源文件,然后重新编译,写入软盘镜像,调试运行。
PiscisOS的整体开发方法就是这样的,在我的知识范围内尽量的使整个开发过程自动化,简单化。其中我认为FASM的使用是最核心的简化方法了,FASM是很新的一个汇编程序,功能十分强大!他能做MASM和NASM能做的所有东西,针对不同的目标程序只需要在源代码中编写一两条宏,不用去关心编译时候的参数,因为FASM唯一的主要参数就是源文件。
今天先写到这里,下一篇文章的内容将会介绍Boot Sector的编写和PiscsOS的整个启动流程。
敬请期待下一篇:PiscisOS开发笔记_3_Boot Sector实现和PiscisOS启动流程