5 提高运行速度(harib12e)

刚开始看到harib12d的成果还觉得挺感动的,过段时间头脑冷静下来以后再看的话,发现task_b_main数数的速度即便在真机环境下运行还是非常慢,我们得想办法提高它的运行速度。Harib10i在7秒钟的时间内可以数到0099969264,相比之下,harib12d也太慢了。任务A和任务B交替运行的情况下,性能下降到原来的一半还可以理解,如果比这个还慢的话就让人无法忍受了。

那运行速度为什么会这么慢呢?因为我们的程序每计1个数就在画面上显示一次,但1秒钟之内刷新100次以上的话,人眼根本就分辨不出来,所以我们不需要计1个数就刷新一次,只要每隔0.01秒刷新一次就足够了。

本次的bootpack.c节选

  1. void task_b_main(struct SHEET *sht_back)
  2. {
  3. struct FIFO32 fifo;
  4. struct TIMER *timer_ts, *timer_put;
  5. int i, fifobuf[128], count = 0;
  6. char s[12];
  7. fifo32_init(&fifo, 128, fifobuf);
  8. timer_ts = timer_alloc();
  9. timer_init(timer_ts, &fifo, 2);
  10. timer_settime(timer_ts, 2);
  11. timer_put = timer_alloc();
  12. timer_init(timer_put, &fifo, 1);
  13. timer_settime(timer_put, 1);
  14. for (;;) {
  15. count++;
  16. io_cli();
  17. if (fifo32_status(&fifo) == 0) {
  18. io_sti();
  19. } else {
  20. i = fifo32_get(&fifo);
  21. io_sti();
  22. if (i == 1) {
  23. sprintf(s, "%11d", count);
  24. putfonts8_asc_sht(sht_back, 0, 144, COL8_FFFFFF, COL8_008484, s, 11);
  25. timer_settime(timer_put, 1);
  26. } else if (i == 2) {
  27. farjmp(0, 3 * 8);
  28. timer_settime(timer_ts, 2);
  29. }
  30. }
  31. }
  32. }

基本上就是这个样子。对了,代码开头的sht_back我们改为作为函数的参数来传递了,关于这一点我们以后会讲到,大家不必担心。

另外,上面的代码还把任务切换计时器超时的时候向FIFO写入的值改为了2。其实不改也没什么问题,只不过因为这个计时器定了0.02秒这个数,所以就顺手改成2了。

还有,count数值的显示格式改成了11位数字,因为运行速度变快了的话,说不定数字位数会不够用呢(笑)。

■■■■■

关于将sht_back的值从HariMain传递过来的方法,((int ) 0x0fec)这样的写法感觉实在是不好看,于是果断废弃了,我们用栈来替代它。

举个例子,load_tr(123);这样的函数调用,如果从汇编语言的角度来考虑的话,参数指定的数值(123)就放在内存中,地址为ESP+4,这是C语言的一个既定机制。

既然有这种机制,那么我们可以反过来利用一下,也就是说,在HariMain里面这样写:

本次的HariMain节选

  1. task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
  2. *((int *) (task_b_esp + 4)) = (int) sht_back;

这样一来,在任务B启动的时候,[ESP+4]这个地址里面就已经存入了sht_back的值,因此我们就欺骗了task_b_main,让它以为自己所接收到的sht_back是作为一个参数传递过来的。

可能有人不明白为什么我们要把task_b_esp的地址减8,减4不就可以了吗?我们当然不能减4,只要仔细思考一下就能搞清楚这里的奥妙。

假设memman_alloc_4k分配出来的内存地址为0x01234000,由于我们申请分配了64KB的内存空间,那么我们可以自由使用的内存地址就是从0x01234000到0x01243fff为止的这一块。如果在这里我们既不减4也不减8,而是直接加上64 * 1024的话,task_b_esp即为0x01244000。如果我们减去4,task_b_esp即为0x01243ffc,但我们写入sht_back的地址是task_b_esp + 4,算下来就变成了0x01244000,如果把4字节的sht_back值写入这个地址的话,就超出了分配给我们的内存范围(0x01234000~0x01243fff),这样不行。

而如果我们减去8,task_b_esp即为0x01243ff8,写入sht_back的地址是task_b_esp + 4,即0x01243ffc,从这个地址向后写入4字节的sht_back值,则正好在分配出来的内存范围(0x01234000~0x01243fff)内完成操作,这样就不会出问题了。

■■■■■

好,我们来运行一下,看看是不是变快了?还有,task_b_main有没有被我们欺骗而顺利接收到sht_back的值呢?如果这一招不成功的话,sht_back的值就会出现异常,画面上也就应该显示不出数字了。“make run”,哇,成功了,而且速度飞快(请注意,右图显示的是程序还没有运行到10秒的状态,这时就已经数到这么大的数字了!)。

5 提高运行速度(harib12e) - 图1

即便是模拟器环境下运行速度也已经相当快了

COLUMN-9 千万不能return?

在这一节中,task_b_main已经变得像一个普通函数一样了,但是在这个函数中千万不能使用return。

return的功能,说到底其实是返回函数被调用位置的一个JMP指令,但这个task_b_main并不是由某段程序直接调用的,因此不能使用return。如果强行return的话,就会像“执行数据”一样发生问题,程序无法正常运行。

HariMain的情况也是一样的,也禁止使用return。

我们在15.1节中讲过,为了记住现在正在执行的指令所在的内存地址,需要使用EIP寄存器,那么return的时候要返回的地址又记录在哪里呢?对于记性不好的CPU来说,肯定会把这个地址保存在某个地方,没错,它就保存在栈中,地址是[ESP]。

因此,我们不仅可以利用[ESP+4],还可以利用[ESP]来欺骗CPU,其实只要向[ESP]写入一个合适的值,告诉CPU应该返回到哪个地址,task_b_main中就可以使用return了。