2 显示单个字符的API(1)(harib17b)

现在我们要开始做显示单个字符的API了哦。说起来其实也不是很难,只要应用程序能用某种方法调用cons_putchar就可以了。

首先我们做一个测试用的应用程序,将要显示的字符编码存入AL寄存器,然后调用操作系统的函数,字符就显示出来了。

本次的hlt.nas(初稿)

  1. [BITS 32]
  2. MOV AL,'A'
  3. CALL cons_putchar的地址)
  4. fin:
  5. HLT
  6. JMP fin

就是这个样子。CALL是一个用来调用函数的指令。在C语言中,goto和函数调用的处理方式完全不同,不过在汇编语言中,CALL指令和JMP指令其实差不多是一码事,它们的区别仅仅在于,当执行CALL指令时,为了能够在接下来执行RET指令时正确返回,会先将要返回的目标地址PUSH到栈中。

关于CALL指令这里想再讲一下。有人可能会想,直接写CALL cons_putchar不就好了吗?然而,hlt.nas这个应用程序在汇编时并不包含操作系统本身的代码,因此汇编器无法得知要调用的函数地址,汇编就会出错。要解决这个问题,必须人工查好地址后直接写到代码中。在对haribote.sys进行make的时候,通过一定的方法我们可以查出cons_putchar的地址,没有问题,那么我们就来查一下地址……且慢!

■■■■■

这样做有个问题,因为cons_putchar是用C语言写的函数,即便我们将字符编码存入寄存器,函数也无法接收,因此我们必须在CALL之前将文字编码推入栈才行,但这样做也太麻烦了。

没办法,我们只好用汇编语言写一个用来将寄存器的值推入栈的函数了。这个函数不是应用程序的一部分,而是写在操作系统的代码中,因此我们要改写的是naskfunc.nas。另一方面,在应用程序中,我们CALL的地址不再是cons_putchar,而是变成了新写的_asm_cons_putchar。

2 显示单个字符的API(1)(harib17b) - 图1

本次的naskfunc.nas节选(初稿)

  1. _asm_cons_putchar:
  2. PUSH 1
  3. AND EAX,0xff ; AHEAX的高位置0,将EAX置为已存入字符编码的状态
  4. PUSH EAX
  5. PUSH cons的地址)
  6. CALL _cons_putchar
  7. ADD ESP,12 ; 将栈中的数据丢弃
  8. RET

PUSH的特点是后进先出,因此这个顺序没问题。

这段程序的问题在于“cons的地址”到底是多少。应用程序是不知道这个地址的,因此让应用程序来指定地址难以实现。唔,那么只能让操作系统把这个地址事先保存在内存中的某个地方了。哪里比较好呢?对了,就保存在BOOTINFO之前的0x0fec这个地址吧。

本次的naskfunc.nas节选(完成版)

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

本次的console.c节选

  1. void console_task(struct SHEET *sheet, unsigned int memtotal)
  2. {
  3. (中略)
  4. cons.sht = sheet;
  5. cons.cur_x = 8;
  6. cons.cur_y = 28;
  7. cons.cur_c = -1;
  8. *((int *) 0x0fec) = (int) &cons; /*这里!*/
  9. (中略)
  10. }

■■■■■

现在操作系统这边的工作已经完成了,因此我们先来“make”一下,注意这里不是“make run”,和之前不太一样,因为应用程序还没有准备好呢,所以我们先只进行“make。”

make完成后,除了haribote.sys之外,还会生成一个叫bootpack.map的文件。之前我们一直忽略这个文件的,不过这次它要派上用场了。

这是一个文本文件,用文本编辑器打开即可,其中应该可以找到这样一行:

  1. 0x00000BE3 : _asm_cons_putchar

这就是_asm_cons_putchar的地址了,因此,我们将地址填在应用程序中:

本次的hlt.nas(完成版)

  1. [BITS 32]
  2. MOV AL,'A'
  3. CALL 0xbe3
  4. fin:
  5. HLT
  6. JMP fin

然后再进行汇编就可以了,很简单吧。

说起来,我们写的这些代码里面,哪个部分是API呢?“MOVE AL,‘A’”和“CALL 0xbe3”就是API了,因为API就是由应用程序来使用操作系统所提供的服务。当然,我们这个是否达到 “服务”的程度就另当别论了。

现在我们的应用程序也已经完成了,可以“make run”了。嘿!然后在命令行窗口里面运行“hlt”。哈!

于是乎,哐叽!qemu.exe出错关闭了!看来笔者遭遇了一个不得了的大bug。在真机环境下无法预料会造成什么后果,因此请大家不要尝试。好吧,下面我们来解决这个bug。