第21天
前面讲到了运行应用程序时由于段地址的原因出错。段地址应该由操作系统指定,对于应用程序来讲是没有段地址的概念,对于应用程序来说main程序就是地址为0的地方。操作系统把应用程序装载进内存的时候指定了段地址,并注册进GDT中。因此如果操作系统要访问应用程序的数据就需要把操作系统给应用程序装载的地址 + 应用程序的偏移地址。
第一步把操作系统给应用程序分配的内存地址保存在内存中,然后执行应用程序的时候访问应用程序的数据时候再将应用程序传递过来的地址 + 给应用程序分配的首地址。
接下来我们要想办法用C语言写应用程序。如果可以用C语言写应用程序那么应用程序的应该是这个样子的:
void api_putchar(int c);
void HariMain(void)
{
api.putchar("A");
return;
}
上面的这段代码我们保存到a.c文件中。我们一步步来,先创建一个api_putchar函数。
GLOBAL _api_putchar
_api_putchar: ; void api_putchar(int c);
MOV EDX,1
MOV AL,[ESP+4] ; c
INT 0x40
RET
这里的api_putchar需要与a.c的编绎结果进行连接。而且make之后也不能直接运行,而要在生成的a.hrb文件的前6个字符修改为eb, 16, 00, 00, 00, cb。这6字节的翻译成汇编语言就是call 0x1b, retf。这样就能直接运行了。
其实说到底所谓的应用程序生成的机器码为什么要链接,怎么链接,我也不知道,还有为什么链接之后还要修改前6个字节才能运行书上没有仔细讲,算了以后再找机会研究,这里先这样继续学下去。
下面讲操作系统的安全。我们的操作系统目前还没有自我保护的能力,像DOS一样随便一个应用程序都可以改写操作系统所占有内存空间的数据,直接导致了系统的崩溃。
为了防止这样的情况发生,就需要使用操作系统的功能,将应用程序的数据段、代码段与操作系统的数据段、代码段分开,使应用程序无法访问系统段里的数据。目前先给应用程序分配64KB的空间。
操作系统代码段 2*8
操作系统数据段 1*8
应用程序代码段 1003*8
应用程序数据段 1004*8
现在理一下应用程序执行的流程:1、输入应用程序对应的文件名;2、操作系统搜索软盘中的文件,相符的话将当前执行的代码跳到应用程序中;3、应用程序如果调用api_putchar,那么就执行0x40号中断,中断处理程序已经被注册为_asm_hrb_api函数;4、这个中断函数中会调用_hrb_api函数;应用程序就这样执行完毕。
由于现在引入了应用程序专有的代码段和数据段,所以在每个流程之前都要进行切换段选择器。上面的第2步中,跳到应用程序之前,应把操作系统的寄存器保存在内存中,然后将应用程序对应的段地址切换进来。如果应用程序调用api,那么在中断处理程序_asm_hrb_api中应先将应用程序对应的寄存器保存起来,然后再将操作系统的寄存器值恢复回去,再进入到第4步,第4步执行完毕后马上将应用程序对应的寄存器的值恢复。
由于我们引入了应用程序对应的数据段,因此应用程序无法读取操作系统使用的内存单元,对于操作系统掌握的资源,应用程序只能以api的方式进入访问,而且访问完之后马上又切换回应用程序的内存。保护了操作系统的安全。
如果应用程序访问了他不该访问的内存单元,操作系统应该拒绝执行这条指令(上面已经完成),再强制结序程序。
要想强制结束程序,只要在中断号0x0d中注册一个函数既可。当应用程序试图破坏操作系统、或者违背操作系统的设置时,就会自动产生0x0d中断,因此该中断也称为异常。
0x0d中断函数强制结束应用程序的方法就是将所有的寄存器恢复到开始执行应用程序的状态之后返回。如果应用程序中直接将段寄存器赋值,也就是将ds赋值为操作系统的段选择符,那么应用程序还是能访问操作系统控制的内存。
在X86的CPU中如果将访问权限加上0x60的话,就可以将段设置为应用程序用,这里候CPU会认为当前正在运行应用程序,那么存入操作系统用的段地址就会产生异常。
如果用这种方法就必须在TSS中注册操作系统用的段地址和esp中。我们在定义TSS结构体的时候,第2个字段为esp0,第3个字段为ss0,我们就杷操作系统的段地址和esp保存到这2个字段中。如果我们把程序的访问权限加上0x60的话,那么操作系统就不能call这个程序也不能jmp到这个程序。那么我们怎么让操作系统运行这个应用程序呢?答案是要使用retf。我们先把应用程序的cs,eip,ss,esp保存到栈中,然后执行ret指令,那么CPU就会以为已经call过了现在要返回原来的程序,把栈中的值赋给相应的寄存器,然后CPU就继续执行指令。只是这次我们事先把运行应用程序所需要的放到栈中,执行这个指令的时候,实际上是跳到了应用程序之中了。
那么我们如何才能从应用程序中返回呢?我们另外写一个用于结束应用程序的api,api_end函数,并规定为这个操作系统写的应用程序都在要程序的最后调用这个api才能正常结束。我看了本章,大概就是讲了这些。而且在定义api中断的时候也需要把这个中断的访问权限加上0x60,表示这是操作系统给应用程序使用的中断。
这一个章节之前讲了很多操用系统运行应用程序时应该进行的段寄存器切换,而在使用应用程序访问权限的时候,却突然CPU能自动切换了,据我推测就该是如果把段的访问权限改成应用程序之后,那么CPU会自动切换不同访问权限程序段之间的段寄存器切换问题。
看到这里可能明白很多对操作系统的疑惑。以前写应用程序的时候以为操作系统的api和一些第三方库是一样的。其实不然,操作系统肯定设置了访问权限。操作系统在开放api的时候肯定也是以中断的方式开放的,在开放中断的方式开放的。其它的比如键盘、鼠标、定时器之类的中断操作系统肯定不会直接开发而是以api的方式让应用程序使用。这一章的很多东西感觉还是不太懂,但是又感觉学到了很多东西,这感觉真好。