30天自制操作系统

5 提高运行速度(harib12e) …… 295

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);

1 2 3 4

for (;;) { count++; io_cli();

if (fifo32_status(&fifo) == 0) { io_sti(); } else {

i = fifo32_get(&fifo); io_sti(); if (i == 1) {

sprintf(s, \

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); } } }

5 6 7 8 9

}

10 11 12 13 14 14 15

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

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

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

■■■■■

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

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

296 …… 第 15 天:多任务(1)

既然有这种机制,那么我们可以反过来利用一下,也就是说,在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秒的状态,这时就已经数到这么大的数字了!。)

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

6 测试运行速度(harib12f) …… 297

COLUMN-9 千万不能 return?

1 2 3 4 5 6 7 8 9

在这一节中,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 了。

66

测试运行速度(harib12f)

我们的程序运行得很快,可是到底有多快呢?我们得想个办法测一下。可能有人会说,别搞 这种节外生枝的玩意儿了,赶快继续往下讲吧!嗯,要说也是,这的确是有点不务正业了,不过 该玩的时候也要玩一玩嘛!

我们向task_b_main添加了一些代码。 本次的bootpack.c节选

void task_b_main(struct SHEET *sht_back) {

struct FIFO32 fifo;

struct TIMER *timer_ts, *timer_put, *timer_1s; int i, fifobuf[128], count = 0, count0 = 0; char s[12];

10 11 12 13 14 14 15

(中略)

timer_1s = timer_alloc();

timer_init(timer_1s, &fifo, 100); timer_settime(timer_1s, 100); for (;;) {

count++; io_cli();

if (fifo32_status(&fifo) == 0) { io_sti(); } else {

i = fifo32_get(&fifo);

298 …… 第 15 天:多任务(1)

io_sti(); if (i == 1) { (中略)

} else if (i == 2) { (中略)

} else if (i == 100) {

sprintf(s, \

putfonts8_asc_sht(sht_back, 0, 128, COL8_FFFFFF, COL8_008484, s, 11); count0 = count;

timer_settime(timer_1s, 100); }

}

}

}

■■■■■

我们来运行一下,先得看看它是不是能正常工作,―make run‖。不错,在模拟器环境下运行 成功。

上面的数字显示的是速度哦

■■■■■

现在我们到真机环境运行一下。哇,好快!果然真机环境就是快,速度已经达到大约 4638200①了。

我们把这个速度和harib10i做个对比。harib10i在7秒内计数到0099969264,即速度为每秒 14281323,相比之下性能是现在的3倍。咦,怎么会这样?如果是2倍的话还可以理解,3倍就有 点过分了。

看到这个结果心里当然会很不爽,我们来找找原因。嗯,每隔0.01秒刷新显示是不是不太好 呢?如果显示计数是导致速度慢的原因,那干脆就别显示了吧。我们把开头的timer_settime ——————————

① 最后两位数字的值经常变动,因此用00代替。

联系客服:779662525#qq.com(#替换为@)