5 提高运行速度(harib12e)
刚开始看到harib12d的成果还觉得挺感动的,过段时间头脑冷静下来以后再看的话,发现task_b_main数数的速度即便在真机环境下运行还是非常慢,我们得想办法提高它的运行速度。Harib10i在7秒钟的时间内可以数到0099969264,相比之下,harib12d也太慢了。任务A和任务B交替运行的情况下,性能下降到原来的一半还可以理解,如果比这个还慢的话就让人无法忍受了。
那运行速度为什么会这么慢呢?因为我们的程序每计1个数就在画面上显示一次,但1秒钟之内刷新100次以上的话,人眼根本就分辨不出来,所以我们不需要计1个数就刷新一次,只要每隔0.01秒刷新一次就足够了。
本次的bootpack.c节选
void task_b_main(struct SHEET *sht_back)
{
struct FIFO32 fifo;
struct TIMER *timer_ts, *timer_put;
int i, fifobuf[128], count = 0;
char s[12];
fifo32_init(&fifo, 128, fifobuf);
timer_ts = timer_alloc();
timer_init(timer_ts, &fifo, 2);
timer_settime(timer_ts, 2);
timer_put = timer_alloc();
timer_init(timer_put, &fifo, 1);
timer_settime(timer_put, 1);
for (;;) {
count++;
io_cli();
if (fifo32_status(&fifo) == 0) {
io_sti();
} else {
i = fifo32_get(&fifo);
io_sti();
if (i == 1) {
sprintf(s, "%11d", count);
putfonts8_asc_sht(sht_back, 0, 144, COL8_FFFFFF, COL8_008484, s, 11);
timer_settime(timer_put, 1);
} else if (i == 2) {
farjmp(0, 3 * 8);
timer_settime(timer_ts, 2);
}
}
}
}
基本上就是这个样子。对了,代码开头的sht_back我们改为作为函数的参数来传递了,关于这一点我们以后会讲到,大家不必担心。
另外,上面的代码还把任务切换计时器超时的时候向FIFO写入的值改为了2。其实不改也没什么问题,只不过因为这个计时器定了0.02秒这个数,所以就顺手改成2了。
还有,count数值的显示格式改成了11位数字,因为运行速度变快了的话,说不定数字位数会不够用呢(笑)。
■■■■■
关于将sht_back的值从HariMain传递过来的方法,((int ) 0x0fec)这样的写法感觉实在是不好看,于是果断废弃了,我们用栈来替代它。
举个例子,load_tr(123);这样的函数调用,如果从汇编语言的角度来考虑的话,参数指定的数值(123)就放在内存中,地址为ESP+4,这是C语言的一个既定机制。
既然有这种机制,那么我们可以反过来利用一下,也就是说,在HariMain里面这样写:
本次的HariMain节选
task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
*((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秒的状态,这时就已经数到这么大的数字了!)。
即便是模拟器环境下运行速度也已经相当快了
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了。