5 不随操作系统版本而改变的API(harib17e)
所以说我们又要改写console.c了。等等,如果修改了操作系统的代码,岂不是_asm_cons_putchar的地址也会像上次那样发生变化?难道说每次我们修改操作系统的代码,都得把应用程序的代码也改一遍?这也太麻烦了。
虽说确实有的操作系统版本一改变,应用程序也得重新编译,不过还有些系统即便版本改变,应用程序也照样可以运行,大家觉得哪种更好呢?
我们的“纸娃娃系统”也需要解决这个问题,把这个搞定之后,我们再考虑命名的事。
■■■■■
解决这个问题的方法其实有很多,这里先为大家介绍其中一种。
CPU中有个专门用来注册函数的地方,也许大家一下子想不起来,笔者说的其实是中断处理程序。在前面我们曾经做过“当发生IRQ-1的时候调用这个函数”这样的设置,大家还记得吗?这是在IDT中设置的。
反正IRQ只有0~15,而CPU用于通知异常状态的中断最多也只有32种,这些都在CPU规格说明书中有明确记载。不过,IDT中却最多可以设置256个函数,因此还剩下很多没有使用的项。
我们的操作系统从这些项里面借用一个的话,CPU应该也不会有什么意见的吧。所以我们就从IDT中找一个空闲的项来用一下。好,我们就选0x40号(其实0x30~0xff都是空闲的,只要在这个范围内任意一个都可以),并将_asm_cons_putchar注册在这里。
本次的dsctbl.c节选
void init_gdtidt(void)
{
(中略)
/* IDT的设置*/
set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x40, (int) asm_cons_putchar, 2 * 8, AR_INTGATE32); /*这里!*/
return;
}
这样一来,我们只要用INT 0x40来代替原来的CALL 2*8:0xbd1就可以调用_asm_cons_putchar了,很方便吧?我们来修改一下应用程序吧。
本次的hlt.nas
[BITS 32]
MOV AL,'h'
INT 0x40
MOV AL,'e'
INT 0x40
MOV AL,'l'
INT 0x40
MOV AL,'l'
INT 0x40
MOV AL,'o'
INT 0x40
RETF
于是程序变成了这个样子。看到这里,直觉敏锐的读者也许已经发现了“跟调用BIOS的时候差不多嘛……”。没错,虽然INT号不同,但通过INT方式调用这一点的确是非常类似。说起来,MS-DOS的API采用的也是这种INT方式。
另外,使用INT指令来调用的时候会被视作中断来处理,用RETF是无法返回的,需要使用IRETD指令。因此,我们还要改写_asm_cons_putchar。
本次的naskfunc.nas节选
_asm_cons_putchar:
STI ;这里!
PUSH 1
AND EAX,0xff ; 将AH和EAX的高位置0,将EAX置为已存入字符编码的状态
PUSH EAX
PUSH DWORD [0x0fec] ; 读取内存并PUSH该值
CALL _cons_putchar
ADD ESP,12 ; 丢弃栈中的数据
IRETD ; 这里!
用INT调用时,对于CPU来说相当于执行了中断处理程序,因此在调用的同时CPU会自动执行CLI指令来禁止中断请求。但我们只是用它来代替CALL使用,这种做法就显得画蛇添足了。我们可不想看到“API处理时键盘无法输入”这样的情况,因此需要在开头添加一条STI指令。
其实,对于这种问题,一般来说可以通过在注册到IDT时修改设置来禁止CPU擅自执行CLI,不过这个有点麻烦,还是算了吧(笑)。话说,今天笔者貌似懒到家了,得反省一下。
■■■■■
好了,修改完成,我们来“make run”一下,结果如下。
正常显示“你好”
嗯,很顺利呢。而且应用程序还比之前小了。
harib17d
的hlt.hrb
:46字节
harib17e
的hlt.hrb
:21字节
你看,用这种方法能把应用程序缩小,厉害吧?这是因为far-CALL指令需要7个字节而INT指令只需要2个字节的缘故。这次修改还真是一箭双雕呢。