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节选

  1. void init_gdtidt(void)
  2. {
  3. (中略)
  4. /* IDT的设置*/
  5. set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);
  6. set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
  7. set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
  8. set_gatedesc(idt + 0x40, (int) asm_cons_putchar, 2 * 8, AR_INTGATE32); /*这里!*/
  9. return;
  10. }

这样一来,我们只要用INT 0x40来代替原来的CALL 2*8:0xbd1就可以调用_asm_cons_putchar了,很方便吧?我们来修改一下应用程序吧。

本次的hlt.nas

  1. [BITS 32]
  2. MOV AL,'h'
  3. INT 0x40
  4. MOV AL,'e'
  5. INT 0x40
  6. MOV AL,'l'
  7. INT 0x40
  8. MOV AL,'l'
  9. INT 0x40
  10. MOV AL,'o'
  11. INT 0x40
  12. RETF

于是程序变成了这个样子。看到这里,直觉敏锐的读者也许已经发现了“跟调用BIOS的时候差不多嘛……”。没错,虽然INT号不同,但通过INT方式调用这一点的确是非常类似。说起来,MS-DOS的API采用的也是这种INT方式。

另外,使用INT指令来调用的时候会被视作中断来处理,用RETF是无法返回的,需要使用IRETD指令。因此,我们还要改写_asm_cons_putchar。

本次的naskfunc.nas节选

  1. _asm_cons_putchar:
  2. STI ;这里!
  3. PUSH 1
  4. AND EAX,0xff ; AHEAX的高位置0,将EAX置为已存入字符编码的状态
  5. PUSH EAX
  6. PUSH DWORD [0x0fec] ; 读取内存并PUSH该值
  7. CALL _cons_putchar
  8. ADD ESP,12 ; 丢弃栈中的数据
  9. IRETD ; 这里!

用INT调用时,对于CPU来说相当于执行了中断处理程序,因此在调用的同时CPU会自动执行CLI指令来禁止中断请求。但我们只是用它来代替CALL使用,这种做法就显得画蛇添足了。我们可不想看到“API处理时键盘无法输入”这样的情况,因此需要在开头添加一条STI指令。

其实,对于这种问题,一般来说可以通过在注册到IDT时修改设置来禁止CPU擅自执行CLI,不过这个有点麻烦,还是算了吧(笑)。话说,今天笔者貌似懒到家了,得反省一下。

■■■■■

好了,修改完成,我们来“make run”一下,结果如下。

5 不随操作系统版本而改变的API(harib17e) - 图1

正常显示“你好”

嗯,很顺利呢。而且应用程序还比之前小了。

harib17dhlt.hrb:46字节

harib17ehlt.hrb:21字节

你看,用这种方法能把应用程序缩小,厉害吧?这是因为far-CALL指令需要7个字节而INT指令只需要2个字节的缘故。这次修改还真是一箭双雕呢。