前言
昨天我们增加了可同时启动的应用程序的数量,窗口也跟着变多了,整个画面变得热闹起来。
话说,在对比color.hrb和color2.hrb的时候我们需要移动窗口,那个时候笔者感到窗口移动的速度很慢。在真机环境下的速度还算可以接受,但在QEMU下就慢得离谱,让人心烦。虽说在真机环境下速度不慢就可以了,但如果速度能再快点总归是件好事。因此,提高窗口移动的速度就成了我们今天的第一个课题。
一、提高窗口移动速度
1.0
导致窗口移动速度慢的原因有很多,其中之一就是sheet_refreshmap的速度太慢。这个函数在sheet_slide中被调用了两次,如果能提高它的速度效果应该会很明显。
这个函数是很久之前写的,因此在修改之前我们再来看一下它的代码。
void sheet_refreshmap(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0)
{...for (h = h0; h <= ctl->top; h++) {sht = ctl->sheets[h];sid = sht - ctl->sheets0; buf = sht->buf;bx0 = vx0 - sht->vx0;by0 = vy0 - sht->vy0;bx1 = vx1 - sht->vx0;by1 = vy1 - sht->vy0;if (bx0 < 0) { bx0 = 0; }if (by0 < 0) { by0 = 0; }if (bx1 > sht->bxsize) { bx1 = sht->bxsize; }if (by1 > sht->bysize) { by1 = sht->bysize; }for (by = by0; by < by1; by++) {vy = sht->vy0 + by;for (bx = bx0; bx < bx1; bx++) {vx = sht->vx0 + bx;if (buf[by * sht->bxsize + bx] != sht->col_inv) {map[vy * ctl->xsize + vx] = sid;}}}}return;
}
怎样才能提高这个函数的速度呢?我们可以尝试将里面的if语句去掉。这个if语句位于三层for循环之中,要被执行成千上万次,因此如果能去掉这个if语句的话,速度应该会有不小的提高。
要去掉这个if语句,我们得先思考一下它的含义。这个if语句的功能是判断图层是否为透明部分,如果强行去掉它的话图层的透明部分就没有了,鼠标指针就会变成一个方块,这样可不行。
但换个角度想想,窗口基本上都是矩形的,没有透明部分,如果仅去掉窗口部分的if判断是没有影响的。
因此,我们在进人bx和by的for循环之前先判断这个图层是否有透明部分,如果有透明部分的话还按现有程序执行,否则执行一个没有if语句的两层循环。
void sheet_refreshmap(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0)
{...for (h = h0; h <= ctl->top; h++) {sht = ctl->sheets[h];sid = sht - ctl->sheets0; buf = sht->buf;bx0 = vx0 - sht->vx0;by0 = vy0 - sht->vy0;bx1 = vx1 - sht->vx0;by1 = vy1 - sht->vy0;if (bx0 < 0) { bx0 = 0; }if (by0 < 0) { by0 = 0; }if (bx1 > sht->bxsize) { bx1 = sht->bxsize; }if (by1 > sht->bysize) { by1 = sht->bysize; }if (sht->col_inv == -1) {/* 无透明色图层专用的高速版 */for (by = by0; by < by1; by++) {vy = sht->vy0 + by;for (bx = bx0; bx < bx1; bx++) {vx = sht->vx0 + bx;map[vy * ctl->xsize + vx] = sid;}}} else {/* 无透明色图层专用的高速版 */for (by = by0; by < by1; by++) {vy = sht->vy0 + by;for (bx = bx0; bx < bx1; bx++) {vx = sht->vx0 + bx;if (buf[by * sht->bxsize + bx] != sht->col_inv) {map[vy * ctl->xsize + vx] = sid;}}}}}return;
}
完工啦!我们赶紧来运行一下看看,“makerun”……·嗯嗯,总之运行起来没有什么问题。
窗口移动的速度呢……唔,也看不太出来,不过感觉好像快了那么一点,就当是成功了吧(笑)。
2.0
这点改善还不够,我们得想办法让速度变得更快才行。其实要想变快,方法还是很多的。
比如sheet_refreshmap中有这样一句:
map[vy * ctl ->xsize + vx] = sid;
我们来琢磨一下这行代码。这个命令的功能是向内存中某个地址写人sid的值,它也位于for循环中,会被反复执行,而且这个地址的后面以及再后面的地址也要写入sid的值。
且慢,这样的话不是有个更好的方法吗?在汇编语言中,如果我们用16位寄存器代替8位寄存器来执行MOV指令的话,相邻的地址中也会同时写人数据,而如果用32位寄存器,仅1条指令就可以同时向相邻的4个地址写人值了。
更重要的是,即便是同时写人4个字节的值,只要指定地址是4的整数倍,指令的执行速度就和1个字节的MOV是相同的。也就是说,速度说不定能提高到原来的4倍!这简直是太强大了,我们一定得试试看。
void sheet_refreshmap(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0)
{...for (h = h0; h <= ctl->top; h++) {sht = ctl->sheets[h];sid = sht - ctl->sheets0; buf = sht->buf;bx0 = vx0 - sht->vx0;by0 = vy0 - sht->vy0;bx1 = vx1 - sht->vx0;by1 = vy1 - sht->vy0;if (bx0 < 0) { bx0 = 0; }if (by0 < 0) { by0 = 0; }if (bx1 > sht->bxsize) { bx1 = sht->bxsize; }if (by1 > sht->bysize) { by1 = sht->bysize; }if (sht->col_inv == -1) {/* 从此开始 */if ((sht->vx0 & 3) == 0 && (bx0 & 3) == 0 && (bx1 & 3) == 0) {/* 无透明色图层专用的高速版(4字节型) */bx1 = (bx1 - bx0) / 4; /* MOV 次数 */sid4 = sid | sid << 8 | sid << 16 | sid << 24;for (by = by0; by < by1; by++) {vy = sht->vy0 + by;vx = sht->vx0 + bx0;p = (int *) &map[vy * ctl->xsize + vx];for (bx = 0; bx < bx1; bx++) {p[bx] = sid4;}}} else {/* 无透明色图层专用的高速版(1字节型) */for (by = by0; by < by1; by++) {vy = sht->vy0 + by;for (bx = bx0; bx < bx1; bx++) {vx = sht->vx0 + bx;map[vy * ctl->xsize + vx] = sid;}}}/* 到此结束 */} else {/* 有透明色图层用的普通版 */for (by = by0; by < by1; by++) {vy = sht->vy0 + by;for (bx = bx0; bx < bx1; bx++) {vx = sht->vx0 + bx;if (buf[by * sht->bxsize + bx] != sht->col_inv) {map[vy * ctl->xsize + vx] = sid;}}}}}return;
}
其实有透明色的情况我们也可以改成4字节型,不过这次我们先不改了。因为修改这里必须考虑透明色的处理,算法会比较复杂,而且现在用透明色的只有鼠标指针,鼠标的图层尺寸很小,两种方式的速度差异不大。
为了让这次的修改发挥最大的效果,我们需要使窗口在x方向上的大小为4的倍数,而且窗口的x坐标也要为4的倍数。目前所有的窗口大小都是4的倍数,所以不需要修改了,而对于窗口坐标,我们需要做AND运算来取整,使打开窗口时的显示位置为4的倍数。
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{...} else if (edx == 5) {sht = sheet_alloc(shtctl);sht->task = task;sht->flags |= 0x10;sheet_setbuf(sht, (char *) ebx + ds_base, esi, edi, eax);make_window8((char *) ebx + ds_base, esi, edi, (char *) ecx + ds_base, 0);sheet_slide(sht, ((shtctl->xsize - esi) / 2) & ~3, (shtctl->ysize - edi) / 2);/*这里!*/sheet_updown(sht, shtctl->top); /* 将窗口图层高度指定为当前鼠标所在图层的高度,鼠标移到上层 */reg[7] = (int) sht;} else if (edx == 6) {...
}
其中3的意思是将3这个数的每个比特位进行取反,也就是等于0xfmmfc,之所以写成3是
因为这样写起来比较短,而且也不会由于的个数太多而不小心写错。
还有一点,当用鼠标拖动窗口时如果目的地坐标不是4的倍数,我们这次的修改也就没有效果了,为了避免这种情况,我们必须保证目的地坐标为4的倍数才行。
void HariMain(void)
{...int j, x, y, mmx = -1, mmy = -1, mmx2 = 0;...for (;;) {...if (fifo32_status(&fifo) == 0) {...}else{if (256 <= i && i <= 511) { ...} else if (512 <= i && i <= 767) {if (mouse_decode(&mdec, i - 512) != 0) {...if ((mdec.btn & 0x01) != 0) {...if (mmx < 0) {...for (j = shtctl->top - 1; j > 0; j--) {...if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) {...if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) {mmx = mx; /* 切换到窗口移动模式 */mmy = my;mmx2 = sht->vx0; /*这里!*/}...}}}}else {/* 如果处于窗口移动模式 */x = mx - mmx; /* 计算鼠标指针移动量 */y = my - mmy;sheet_slide(sht, (mmx2 + x + 2) & ~3, sht->vy0 + y); /*这里!*/mmy = my; /* 更新到移动后的坐标 */}} else {mmx = -1; }}
}
变量mmx2的功能是保存移动前的sht->vx0的值。
sheet_slide的地方我们做了AND运算,之所以要先加2再做AND,是因为只做AND的话就像直接舍去小数一样,容易造成窗口往左移,而如果先加上2就相当于做了四舍五人,使往左和往右移的几率对等哦。
好了,我们来“makerun”。哇哦!好快!如果说之前移动窗口是“唰唰唰”的感觉的话,现在比之前变得更加流畅了,差不多是“嗖嗖嗖”这样吧。大家可以在CPU特别慢的电脑上用QEMU来对比一下,一定能明显感觉到差距的。
3.0
我们发现一次性写人4个字节这个办法可以有效地提高速度,既然如此,这个办法是不是也能用在sheet_refreshmap以外的函数中呢?于是我们首先想到了sheet_refreshsub,窗口移动的时候也调用了这个函数,因此通过修改它可以提高窗口移动的速度,此外其他一些地方也会调用这个函数,如果顺利的话,系统整体的绘图速度都会有所提升,真是令人兴奋呀。
void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0, int h1)
{int h, bx, by, vx, vy, bx0, by0, bx1, by1, bx2, sid4, i, i1, *p, *q, *r;...for (h = h0; h <= h1; h++) {...if ((sht->vx0 & 3) == 0) {/* 4字节 */i = (bx0 + 3) / 4; /* bx0除以4(小数进位) */i1 = bx1 / 4; /* bx1除以4(小数舍去) */i1 = i1 - i;sid4 = sid | sid << 8 | sid << 16 | sid << 24;for (by = by0; by < by1; by++) {vy = sht->vy0 + by;for (bx = bx0; bx < bx1 && (bx & 3) != 0; bx++) { /* 前面被4除多余的部分逐个字节写入 */vx = sht->vx0 + bx;if (map[vy * ctl->xsize + vx] == sid) {vram[vy * ctl->xsize + vx] = buf[by * sht->bxsize + bx];}}vx = sht->vx0 + bx;p = (int *) &map[vy * ctl->xsize + vx];q = (int *) &vram[vy * ctl->xsize + vx];r = (int *) &buf[by * sht->bxsize + bx];for (i = 0; i < i1; i++) { /* 前面被4除多余的部分逐个字节写入 */if (p[i] == sid4) {q[i] = r[i];/* 估计大多数会是这种情况,因此速度会变快 */} else {bx2 = bx + i * 4;vx = sht->vx0 + bx2;if (map[vy * ctl->xsize + vx + 0] == sid) {vram[vy * ctl->xsize + vx + 0] = buf[by * sht->bxsize + bx2 + 0];}if (map[vy * ctl->xsize + vx + 1] == sid) {vram[vy * ctl->xsize + vx + 1] = buf[by * sht->bxsize + bx2 + 1];}if (map[vy * ctl->xsize + vx + 2] == sid) {vram[vy * ctl->xsize + vx + 2] = buf[by * sht->bxsize + bx2 + 2];}if (map[vy * ctl->xsize + vx + 3] == sid) {vram[vy * ctl->xsize + vx + 3] = buf[by * sht->bxsize + bx2 + 3];}}}for (bx += i1 * 4; bx < bx1; bx++) { /* 后面被4除多余的部分逐个字节写入 */vx = sht->vx0 + bx;if (map[vy * ctl->xsize + vx] == sid) {vram[vy * ctl->xsize + vx] = buf[by * sht->bxsize + bx];}}}} else {/* 1字节型 */for (by = by0; by < by1; by++) {vy = sht->vy0 + by;for (bx = bx0; bx < bx1; bx++) {vx = sht->vx0 + bx;if (map[vy * ctl->xsize + vx] == sid) {vram[vy * ctl->xsize + vx] = buf[by * sht->bxsize + bx];}}}}}return;
}}
这样一来,当窗口显示位置为4的倍数时速度就会变快。重绘画面时,重绘范围不一定为4的倍数,因此我们也考虑了这种情况的处理方法:当前面有余数时将余数部分逐个字节处理,后面有余数时也一样。不过,即便窗口本身没有透明色,当它和别的窗口或者鼠标重叠时我们也不能直接往相邻的内存地址中写人数据,因此for循环中用来判断map的值是否等于sid的if语句还是不能去掉的。
我们来“makerun”吧。哦哦,好快!移动起来嗖嗖的,昨天我们还在忍受着龟速呢,现在想想就跟做梦似的,耶!
4.0
虽说窗口移动的速度已经快了很多,但还是无法完全跟上鼠标指针那矫健的身影(啊,在真机环境下当然还是跟得上的,这里是说在QEMU中运行的情形,大家别误会哦)。更进一步的提速我们现在还做不到,不过我们可以动点脑筋,在真正的速度不变的情况下,给用户带来好像变快了的错觉(笑)。
说起来,用“纸娃娃系统”之所以会感到速度慢,是因为移动窗口的时候,窗口移动的速度赶不上鼠标的速度,在放开鼠标键之后窗口还在那里挪动,因此我们只要针对这个现象想想办法就可以了。
为什么明明已经放开了鼠标键,窗口却还在挪动呢?这是因为伴随图层移动所进行的绘图操作非常消耗时间,导致系统来不及处理FIFO中的鼠标移动数据。那么我们可以在接收到鼠标移动数据后不立即进行绘图操作,但如果一直不绘图的话鼠标和窗口就静止不动了,那不就没意义了吗?我们可以等FIFO为空时再进行绘图操作嘛。
void HariMain(void)
{...int mx, my, i, new_mx = -1, new_my = 0, new_wx = 0x7fffffff, new_wy = 0; /*这里!*/...for (;;) {...if (fifo32_status(&fifo) == 0) {/*FIFO为空,当存在搁置的绘图操作时立即执行*//* 从此开始 */if (new_mx >= 0) {io_sti();sheet_slide(sht_mouse, new_mx, new_my);new_mx = -1;} else if (new_wx != 0x7fffffff) {io_sti();sheet_slide(sht, new_wx, new_wy);new_wx = 0x7fffffff;} else {task_sleep(task_a);io_sti();}/* 到此结束 */} else {...} else if (512 <= i && i <= 767) { /* �}�E�X�f�[�^ */if (mouse_decode(&mdec, i - 512) != 0) {...new_mx = mx; /*这里!*/new_my = my; /*这里!*/if ((mdec.btn & 0x01) != 0) {/* 鼠标左键按下 */if (mmx < 0) {...for (j = shtctl->top - 1; j > 0; j--) {...if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) {if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {...if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) {mmx = mx; /*切换到窗口移动模式*/mmy = my;mmx2 = sht->vx0;new_wy = sht->vy0; /* 这里!*/}...}}
}
} else {/* 如果处于窗口移动模式 */x = mx - mmx; /* 计算鼠标指针移动量 */y = my - mmy;new_wx = (mmx2 + x + 2) & ~3; /*这里!*/new_wy = new_wy + y; /*这里!*/mmy = my; /**更新到移动后的坐标*/}} else {/*没有按下鼠标左键*/mmx = -1; /*切换到一般模式*/if (new_wx != 0x7fffffff) {/*从此开始*/sheet_slide(sht, new_wx, new_wy); /* 固定图层位置 */new_wx = 0x7fffffff;}/*到此结束*/}}}}}
}
}
这次我们增加了new_mx和new_wy两个变量,并将原来的sheet_slide(sht_mouse,mx,my〉;改成了new_mx=mx;new_my=my;,也就是说,我们并不真的移动鼠标图层的位置,而是将移动后的坐标暂且保存起来,当FIFO为空时,再执行sheet_slide(sht_mouse,new_mx,new_my);。
窗口移动我们也采用相同的方法,只不过有一点小小的区别,代表FIFO为空时不需要执行sheet_slide的值从-1变成了0x7fmmmff,这是因为鼠标的坐标不可能为负数,但窗口的坐标却有可能为负数,因此用-1会造成歧义,我们只好改用另外一个不可能会出现的值,即0x7mmmf。
当放开鼠标左键退出窗口移动模式时,即便FIFO不为空也需要立即更新窗口的位置,这是因为用户可能马上会去移动别的窗口,那样的话sht变量的值就会发生变化,因此我们必须在sht变量的值改变之前将当前窗口移动到指定的位置。
好了,我们来“makerun”……哇!即便在CPU很慢的电脑上用QEMU运行我们的系统,窗口也能跟鼠标同步快速移动,太好了!
二、启动时只打开一个命令行窗口
昨天我们将命令行窗口的数量增加到了两个,还把碍眼的task_a窗口给去掉了,今天我们要让它看上去更像一个普通的操作系统。
说到这里,首先想到的是系统启动时所打开的命令行窗口数量,普通的操作系统没有一启动就打开两个命令行窗口的吧?一般都是先打开一个命令行窗口,然后根据需要增加。下面我们就将启动时显示的命令行窗口数量改为一个,并且实现可以随意启动新命令行窗口的功能吧。
这里我们需要考虑一下,如果启动一个新窗口需要让用户如何操作呢?比如说,在
Windows2000的命令行窗口中输人“startcmd.exe”,就会出现一个新的命令行窗口。那么我们就来模仿一下这个方法,先编写一个用来启动新窗口的命令吧。
不过,实际使用操作系统的用户一般是在运行某个程序的同时突然想做另外一件事情,才需要开新窗口的。这时,如果只能通过命令来启动新窗口,就必须先强制关闭现在正在运行的应用程序,再往命令行窗口中输人命令才行——这也太麻烦了。
在Windows中,即便不在命令行中输人命令,只通过鼠标的操作也可以打开新的命令行窗口(比如可以通过开始菜单),我们的系统也可以借鉴一下这种方法。不过鼠标点击开始菜单这种方式实现起来太难,我们还是做快捷键吧,可以规定按下Shift+F2就打开一个新的命令行窗口。之所以用这个组合键,是因为程序改起来方便,如果不喜欢这个组合键的话,完全可以根据下面的范例自行修改哦。
void HariMain(void)
{.../* sht_cons */sht_cons[0] = open_console(shtctl, memtotal);/* 这里 */sht_cons[1] = 0; /* 未打开状态 这里 */...sheet_slide(sht_back, 0, 0);sheet_slide(sht_cons[0], 32, 4); /* 这里 */sheet_slide(sht_mouse, mx, my);sheet_updown(sht_back, 0);sheet_updown(sht_cons[0], 1); /* 这里 */sheet_updown(sht_mouse, 2); /* 这里 */key_win = sht_cons[0];keywin_on(key_win);for (;;) {...if (fifo32_status(&fifo) == 0) {...}else {...if (256 <= i && i <= 511) {...if (i == 256 + 0x3c && key_shift != 0 && sht_cons[1] == 0) { /* Shift+F2 */sht_cons[1] = open_console(shtctl, memtotal);sheet_slide(sht_cons[1], 32, 4);sheet_updown(sht_cons[1], shtctl->top);/* 自动将输入焦点切换到新打开的命令行窗口(这样比较方便吧?) */keywin_off(key_win);key_win = sht_cons[1];keywin_on(key_win);}...} else if (512 <= i && i <= 767) { /*鼠标数据*/...}}
}
}}}}struct SHEET *open_console(struct SHTCTL *shtctl, unsigned int memtotal)
{struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;struct SHEET *sht = sheet_alloc(shtctl);unsigned char *buf = (unsigned char *) memman_alloc_4k(memman, 256 * 165);struct TASK *task = task_alloc();int *cons_fifo = (int *) memman_alloc_4k(memman, 128 * 4);sheet_setbuf(sht, buf, 256, 165, -1); /* 无透明色 */make_window8(buf, 256, 165, "console", 0);make_textbox8(sht, 8, 28, 240, 128, COL8_000000);task->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 12;task->tss.eip = (int) &console_task;task->tss.es = 1 * 8;task->tss.cs = 2 * 8;task->tss.ss = 1 * 8;task->tss.ds = 1 * 8;task->tss.fs = 1 * 8;task->tss.gs = 1 * 8;*((int *) (task->tss.esp + 4)) = (int) sht;*((int *) (task->tss.esp + 8)) = memtotal;task_run(task, 2, 2); /* level=2, priority=2 */sht->task = task;sht->flags |= 0x20; /* 有光标 */fifo32_init(&task->fifo, 128, cons_fifo, task);return sht;
}
首先,我们将打开命令行窗口的程序封装成了一个单独的函数(open_console),然后将没有打开的窗口的sht_cons[]置为0以示区别。当按下Shift+F2且第2个命令行窗口处于未打开状态时将其打开。
那么我们来“makerun”吧,先来运行我们特别喜欢的color2.hrb,然后按下Shift+F2打开新的命令行窗口,再运行lines.hrb。成功了,好开心呀!
三、增加更多的命令行窗口
命令行窗口最多只能打开两个,这太不方便了,要是能打开更多的窗口就好了。那我们写一个sht_cons[100]之类的不就行了吗?不过仔细想想看,这个sht_cons[]到底是用来干什么的呢?怎么想也想不通啊,特地定义这个变量保存了一个值,实际上却根本没有用到!
因此,我们干脆不用sht_cons[]了,如果顺利的话,命令行窗口将不再有数量的限制,只要内存空间足够,就可以想开多少开多少。
void HariMain(void)
{...struct SHEET *sht_back, *sht_mouse; /*刪掉了sht_cons[2]*/.../* sht_cons */key_win = open_console(shtctl, memtotal);...sheet_slide(sht_back, 0, 0);sheet_slide(key_win, 32, 4); /* 这里!*/sheet_slide(sht_mouse, mx, my);sheet_updown(sht_back, 0);sheet_updown(key_win, 1); /* 这里!*/sheet_updown(sht_mouse, 2);keywin_on(key_win);for (;;) {...if (fifo32_status(&fifo) == 0) {...}else {if (256 <= i && i <= 511) { ...if (i == 256 + 0x3c && key_shift != 0) { /* Shift+F2 *//* 自动将输入焦点切换到新打开的命令行窗口(这样比较方便吧?) */keywin_off(key_win); /*从此开始*/key_win = open_console(shtctl, memtotal);sheet_slide(key_win, 32, 4);sheet_updown(key_win, shtctl->top); /**到此结束*/keywin_on(key_win);}...}
}
程序写好了,之前的程序中我们是先向sht_cons[]赋值,然后再由sht_cons[]赋值给key_win,现在我们改成直接向key_win赋值了。
这样能不能行呢?改得太彻底了,笔者有点担心呢。不过这样也无济于事,还是“makerun”一下试试看吧·····哦哦,成功了!我们可以打开好多个命令行窗口了,运行多个应用程序也没问题哦,太棒了!
四、关闭命令行窗口
1.0
现在,我们对新开命令行窗口这个功能已经很满意了,不过如果一高兴刹不住车打开太多命令行窗口的话,画面就会变得拥挤不堪,同时也会浪费内存,所以我们得想个办法来关闭命令行窗口才行。
在Windows的命令行窗口中,输人“exit”命令就可以关闭当前窗口,我们也来照猫画虎,给系统增加一个exit命令吧。
在关闭一个命令行窗口时系统需要做些什么事呢?首先需要将创建该窗口时所占用的内存空间全部释放出来,然后还需要释放窗口的图层和任务结构。咦,问题来了,在创建任务时我们为命令行窗口准备了专用的栈,却没有将这个栈的地址保存起来,这样的话就无法执行释放操作了。怎么办呢?我们可以在TASK结构中添加一个cons_stack成员,用来保存栈的地址。
bootpack.h
struct TASK {int sel, flags; /* sel为GDT编号 */int level, priority;struct FIFO32 fifo;struct TSS32 tss;struct CONSOLE *cons;int ds_base, cons_stack; /* 这里!*/
};
bootpack.c
struct SHEET *open_console(struct SHTCTL *shtctl, unsigned int memtotal)
{...task->cons_stack = memman_alloc_4k(memman, 64 * 1024);task->tss.esp = task->cons_stack + 64 * 1024 - 12;...
}void close_constask(struct TASK *task)
{struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;task_sleep(task);memman_free_4k(memman, task->cons_stack, 64 * 1024);memman_free_4k(memman, (int) task->fifo.buf, 128 * 4);task->flags = 0; /* 替代task_free(task); */return;
}void close_console(struct SHEET *sht)
{struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;struct TASK *task = sht->task;memman_free_4k(memman, (int) sht->buf, 256 * 165);sheet_free(sht);close_constask(task);return;
}
一上来我们只写了一个用来结束命令行窗口任务的close_constask函数,不过关闭命令行窗口还需要关闭图层,于是就又写了一个close_console函数,在关闭图层之后调用close_constask。其实,将这个两个功能都整合close_console里面也可以,不过我们后面还需要只关闭任务不关闭图层的功能,因此在这里我们先分成两个函数来写。
在close_constask中,一开始我们先让任务进人休眠状态,这是为了将任务从等待切换列表中安全地剥离出来,因为这样一来就绝对不会切换到该任务,我们就可以安全地释放栈和FIFO缓冲区了。当全部内存空间都释放完毕之后,为了taskalloc下次能够重新利用这些内存空间,我们还需要将flags置为0。
到这里bootpack.c的准备就完成了,下面我们来编写exit命令。
void cons_runcmd(char *cmdline, struct CONSOLE *cons, int *fat, int memtotal)
{...} else if (strcmp(cmdline, "exit") == 0) {cmd_exit(cons, fat);} else if (cmdline[0] != 0) {...
}void cmd_exit(struct CONSOLE *cons, int *fat)
{struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;struct TASK *task = task_now();struct SHTCTL *shtctl = (struct SHTCTL *) *((int *) 0x0fe4);struct FIFO32 *fifo = (struct FIFO32 *) *((int *) 0x0fec);timer_cancel(cons->timer);memman_free_4k(memman, (int) fat, 4 * 2880);io_cli();fifo32_put(fifo, cons->sht - shtctl->sheets0 + 768); /* 768 - 1023 */io_sti();for (;;) {task_sleep(task);}
}
exit命令的执行部分中,首先我们需要取消控制光标闪烁的定时器,然后将FAT用的内存空间释放,最后调用close_console关闭命令行窗口和自身的任务···咦?这里看出问题了吗?
如果在cmd_exit中调用close_console的话,就相当于close_constask中的task_sleep对自己这个任务本身执行休眠,那么之后的程序就都无法继续执行下去了。因此,我们需要让task_a来替我们执行这个操作(注:虽然现在已经没有task_a这个窗口了,但是task_a这个任务依然存在,它负责处理鼠标指针的移动、将键盘输人的数据分配给各命令行窗口等工作)。
那么我们可以从命令行窗口任务向task_a任务发送一个数据,请task_a帮忙关闭命令行窗口任务。task_a的FIFO地址保存在OxOfec这个地址(等一下我们会修改bootpack.c让它将地址写人这里)只要读取出来并发送数据就可以了。为了防止发送数据期间产生中断请求导致发送失败,
我们将发送数据的程序两边加上cli和sti。发送完成之后,既然结束任务的处理已经交给task_a,那么命令行窗口任务本身也没有什么可做的了,接下来直接休眠就可以了。
我们还需要修改HariMain使其能够处理来自命令行窗口的768~1023的数据,另外,从现在开始可能会出现画面上一个窗口都没有的情况(如果关闭了所有的命令行窗口的话),因此我们必须对这样的情况做出应对。
void HariMain(void)
{...*((int *) 0x0fec) = (int) &fifo;...for (;;) {if (fifo32_status(&fifo) == 0) {...} else {...if (key_win != 0 && key_win->flags == 0) { /* 窗口被关闭 */ /*从此开始*/if (shtctl->top == 1) { /* 当画面上只刺鼠标和背景时 */key_win = 0;} else {key_win = shtctl->sheets[shtctl->top - 1];keywin_on(key_win);} }/*到此结束*/if (256 <= i && i <= 511) { /*键盘数据**/...if (s[0] != 0 && key_win != 0) { /* 一般字符、退格键和回车键 */ /*这里!*/fifo32_put(&key_win->task->fifo, s[0] + 256);}if (i == 256 + 0x0f && key_win != 0) { /* Tab */ /*这里!*/...}...if (i == 256 + 0x3b && key_shift != 0 && key_win != 0) { /* Shift+F1 *//*这里!*/...}if (i == 256 + 0x3c && key_shift != 0) { /* Shift+F2 *//* 自动将输入焦点切换到断打开的命令行窗口(这样比较方便吧?) */if (key_win != 0) {/*从此开始*/keywin_off(key_win);}/**到此结束*/key_win = open_console(shtctl, memtotal);sheet_slide(key_win, 32, 4);sheet_updown(key_win, shtctl->top);keywin_on(key_win);}...} else if (512 <= i && i <= 767) { /* 鼠标数据 */...}else if (768 <= i && i <= 1023) { /* 命令行窗口关闭处理 */ /*这里!*/close_console(shtctl->sheets0 + (i - 768)); /*这里!*/}}}
}
我们先来看最后关于“命令行窗口关闭处理”那一段。这段程序比较简单,只是完成命令行窗口所委托的操作而已。至于要关闭的图层句柄,是通过将命令行窗口发送过来的数据减去768计算出来的。
除此之外,我们还修改了关于key_win的部分,当画面上一个窗口都没有的情况下,自然也没有窗口处于输人模式,这时我们将key_win置为0,而通常情况下key_win是不可能为0的,这样就可以清楚地区别开来。当key_win为0时,字符输人和Shift+F1没有任何作用,因此我们对于这两种键盘输人不进行任何处理。
现在到了欢乐的“makerun”时间,首先是刚刚启动完毕的画面。
然后,我们按Shift+F2新开几个窗口,并在窗口中输人exit命令。命令行窗口成功关闭了,成功了哦!
2.0
做到这一步,接下来我们就来实现用鼠标关闭命令行窗口的功能。当鼠标点击窗口上的“x”按钮时,向命令行窗口任务发送4这个数据,命令行窗口接收到这个数据后则开始执行exit命令的程序。
话说,鼠标的点击是在task_a中处理的,为什么不直接在task_a中调用close_console,而要绕这么个弯子呢?因为如果直接在taska中关闭命令行窗口的话,由窗口自身所管理的释放定时器及FAT内存空间的部分就难以实现了,因此我们还是选择向命令行窗口发送通知数据这种方式。
由于这次我们只需要将已经实现的功能通过鼠标来进行操作,所以修改起来比较简单。
void HariMain(void)
{...*((int *) 0x0fec) = (int) &fifo;...for (;;) {if (fifo32_status(&fifo) == 0) {...} else {...} else if (512 <= i && i <= 767) { /* 鼠标数据 */if (mouse_decode(&mdec, i - 512) != 0) {...if ((mdec.btn & 0x01) != 0) {/*按下左键的情形*/if (mmx < 0) {...if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) {/* 点击“x”
按钮 */if ((sht->flags & 0x10) != 0) { /* /**是否为应用程序窗口?*/task = sht->task;cons_putstr0(task->cons, "\nBreak(mouse) :\n");io_cli(); /* 禁止在强制结束处理时切换任务 */task->tss.eax = (int) &(task->tss.esp0);task->tss.eip = (int) asm_end_app;io_sti();} else { /* 命令行窗口 */ /*从此开始*/task = sht->task;io_cli();fifo32_put(&task->fifo, 4);io_sti(); /*到此结束*/}}break;...
}
我们在bootpack.c中仅仅增加了5行代码。
接下来是console.c,这里其实仅仅增加了3行代码。
void console_task(struct SHEET *sheet, int memtotal)
{...for (;;) {...if (fifo32_status(&task->fifo) == 0) {task_sleep(task);io_sti();} else {...if (i == 4) { /* 点击命令行窗口的“x”按钮 */ /*从此开始*/cmd_exit(&cons, fat);}/*到此结束*/
}
好,完工了,简单就是好啊,如果一直都这么简单的话,笔者也能轻松多了…···哦,对了,我们还是先“make run”吧。哇,按“x”按钮可以顺利关闭命令行窗口了,成功了!
顺便说一句,在应用程序运行的时候,点击命令行窗口的“x”按钮是不会关闭命令行窗口的。因为如果应用程序运行中关闭了命令行窗口,万一程序调用了显示字符的API,就不知道会造成什么后果了。
五、start命令
今天时间也不早了,差不多该结束了,不过30天大限将至,我们还剩好多东西没有做呢,因此今天大家就辛苦一下,再努把力吧。
我们在26天中提起过,Windows的命令行窗口里有一个start命令,它的功能是可以打开一个新的命令行窗口并运行指定的应用程序。讲这么多不如自己实践一下,在Windows中输人“start”试试看。
如果我们也有这个功能该多方便啊,那我们就来编写一个start命令吧。
void cons_runcmd(char *cmdline, struct CONSOLE *cons, int *fat, int memtotal)
{...} else if (strncmp(cmdline, "start ", 6) == 0) {cmd_start(cons, cmdline, memtotal);} else if (cmdline[0] != 0) {...
}void cmd_start(struct CONSOLE *cons, char *cmdline, int memtotal)
{struct SHTCTL *shtctl = (struct SHTCTL *) *((int *) 0x0fe4);struct SHEET *sht = open_console(shtctl, memtotal);struct FIFO32 *fifo = &sht->task->fifo;int i;sheet_slide(sht, 32, 4);sheet_updown(sht, shtctl->top);/* 将命令行输入的字符串逐字复制到新的命令行窗口中 */for (i = 6; cmdline[i] != 0; i++) {fifo32_put(fifo, cmdline[i] + 256);}fifo32_put(fifo, 10 + 256); /* Enter */cons_newline(cons);return;
}
好,完工了,很简单吧。我们来“make run”,输人“start color2”……哦,成功了!
五、ncst命令
本节要进行今天的最后一项修改。
用start命令启动应用程序看起来很不错,但如果运行color这样的程序的话,我们并不希望真的新开一个命令行窗口出来,反倒是没有这个多余的窗口比较好。那么下面我们就来做一个不打开新命令行窗口的start命令吧,给它起个名字,叫做“no console start”,简称ncst命令。
这样,我们可以根据需要来选择用哪个命令:当希望运行程序的同时打开新的命令行窗口时,用start命令;而当不需要打开新的命令行窗口时,就用ncst命令。
不过,不打开命令行窗口而直接运行应用程序到底应该怎样实现呢?想来想去,总觉得要改的地方实在太多了,我们还是一步一步慢慢来吧。其实我们可以换个思路,不要一味去想“没有命令行窗口该怎样处理”,而可以“想办法禁止向命令行显示内容”(换句话说,用ncst命令启动的应用程序会忽略字符串显示API的调用)。
好了,我们先从添加ncst命令开始做起吧。
我们将没有窗口的命令行任务的cons->sht规定为0。在没有窗口的情况下,执行mem命令和cls命令也没有用,因此我们将这些命令全部忽略。
void cons_runcmd(char *cmdline, struct CONSOLE *cons, int *fat, int memtotal)
{if (strcmp(cmdline, "mem") == 0 && cons->sht != 0) { /*从此开始*/cmd_mem(cons, memtotal);} else if (strcmp(cmdline, "cls") == 0 && cons->sht != 0) {cmd_cls(cons);} else if (strcmp(cmdline, "dir") == 0 && cons->sht != 0) {cmd_dir(cons);} else if (strncmp(cmdline, "type ", 5) == 0 && cons->sht != 0) { /**到此结束*/cmd_type(cons, fat, cmdline);} else if (strcmp(cmdline, "exit") == 0) {cmd_exit(cons, fat);} else if (strncmp(cmdline, "start ", 6) == 0) {cmd_start(cons, cmdline, memtotal);} else if (strncmp(cmdline, "ncst ", 5) == 0) { /*这里!*/cmd_ncst(cons, cmdline, memtotal); /*这里!*/} else if (cmdline[0] != 0) {if (cmd_app(cons, fat, cmdline) == 0) {cons_putstr0(cons, "Bad command.\n\n");}}return;
}void cmd_ncst(struct CONSOLE *cons, char *cmdline, int memtotal)
{struct TASK *task = open_constask(0, memtotal);struct FIFO32 *fifo = &task->fifo;int i;/* 将命令行输入的字符事逐字复制到新的命令行窗口中 */for (i = 5; cmdline[i] != 0; i++) {fifo32_put(fifo, cmdline[i] + 256);}fifo32_put(fifo, 10 + 256); /* Enter */cons_newline(cons);return;
}
cmd_ncst是照着cmd_start的样子写的,其中open_constask这个函数我们接下来会写在bootpack.c中。
当cons->sht为0时,要禁用命令行窗口的字符显示等所有操作,因此我们需要修改与其相关的函数。
// console.c
void cons_putchar(struct CONSOLE *cons, int chr, char move)
{if (s[0] == 0x09) { /* 制表符 */for (;;) {if (cons->sht != 0) { /*从此开始*/putfonts8_asc_sht(cons->sht, cons->cur_x, cons->cur_y, COL8_FFFFFF, COL8_000000, " ", 1);} /*到此结束*/cons->cur_x += 8;if (cons->cur_x == 8 + 240) {cons_newline(cons);}if (((cons->cur_x - 8) & 0x1f) == 0) {break; }}} else if (s[0] == 0x0a) { /* 换行 */cons_newline(cons);} else if (s[0] == 0x0d) { /* 回车 *//* 先不执行操作 */} else { /* 一般字符 */if (cons->sht != 0) { /*从此开始*/putfonts8_asc_sht(cons->sht, cons->cur_x, cons->cur_y, COL8_FFFFFF, COL8_000000, s, 1);} /*到此结束*/if (move != 0) {cons->cur_x += 8;if (cons->cur_x == 8 + 240) {cons_newline(cons);}}}return;
}void cons_newline(struct CONSOLE *cons)
{int x, y;struct SHEET *sheet = cons->sht;if (cons->cur_y < 28 + 112) {cons->cur_y += 16; /* 下一行 */} else {/* 滚动 */if (sheet != 0) { /*这里!*/.../*这里!*/sheet_refresh(sheet, 8, 28, 8 + 240, 28 + 128);}}cons->cur_x = 8;return;
}
本以为相关的函数很多,所以要改动的地方也会很多,不过这么一看其实也没多少嘛。
接下来我们来修改console_task。修改的要点是,当不显示命令行窗口时,禁用一些不必要的处理,并且当命令执行完毕时,立即结束命令行窗口任务(应用程序运行完毕后,这个命令行窗口任务就派不上什么用场了。因为画面上不显示命令行窗口,也就无法输人其他命令,也不能执行关闭操作,所以我们需要使其在命令执行完毕时自动终止任务)。
// console.c
void console_task(struct SHEET *sheet, int memtotal)
{...if (sheet != 0) {cons.timer = timer_alloc();timer_init(cons.timer, &task->fifo, 1);timer_settime(cons.timer, 50);}...for (;;) {io_cli();if (fifo32_status(&task->fifo) == 0) {task_sleep(task);io_sti();} else {...if (256 <= i && i <= 511) {...if (256 <= i && i <= 511) { /* �L�[�{�[�h�f�[�^�i�^�X�NA�o�R�j */if (i == 8 + 256) {...}} else if (i == 10 + 256) {/* Enter *//* 用空白擦除光标后换行 */cons_putchar(&cons, ' ', 0);cmdline[cons.cur_x / 8 - 2] = 0;cons_newline(&cons);cons_runcmd(cmdline, &cons, fat, memtotal); /*执行命令*/if (sheet == 0) { /*从此开始*/cmd_exit(&cons, fat);}/*到此结束*//*显示提示符*/cons_putchar(&cons, '>', 1);} else {...}}/*重新显示光标*//*从此开始*/if (sheet != 0) {if (cons.cur_c >= 0) {boxfill8(sheet->buf, sheet->bxsize, cons.cur_c, cons.cur_x, cons.cur_y, cons.cur_x + 7, cons.cur_y + 15);}sheet_refresh(sheet, cons.cur_x, cons.cur_y, cons.cur_x + 8, cons.cur_y + 16);/*到此结束*/}}}
}
cmdexit也需要修改一下,添加用于无命令行窗口情况下的任务结束处理。
// console.c
void cmd_exit(struct CONSOLE *cons, int *fat)
{struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;struct TASK *task = task_now();struct SHTCTL *shtctl = (struct SHTCTL *) *((int *) 0x0fe4);struct FIFO32 *fifo = (struct FIFO32 *) *((int *) 0x0fec);if (cons->sht != 0) {timer_cancel(cons->timer);}memman_free_4k(memman, (int) fat, 4 * 2880);io_cli();if (cons->sht != 0) { /*从此开始*/fifo32_put(fifo, cons->sht - shtctl->sheets0 + 768); /* 768 - 1023 */} else {fifo32_put(fifo, task - taskctl->tasks0 + 1024); /* 1024 - 2023 */} /**到此结束*/io_sti();for (;;) {task_sleep(task);}
}
有命令行窗口时,我们可以通过图层的地址告诉taska需要结束哪个任务,而无命令行窗口的情况下,这种方法就用不了了,因此在这里我们将TASK结构的地址告诉task_a。
接下来轮到bootpack.c了,首先来编写与openconstask相关的部分。
struct TASK *open_constask(struct SHEET *sht, unsigned int memtotal)
{struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;struct TASK *task = task_alloc();int *cons_fifo = (int *) memman_alloc_4k(memman, 128 * 4);task->cons_stack = memman_alloc_4k(memman, 64 * 1024);task->tss.esp = task->cons_stack + 64 * 1024 - 12;task->tss.eip = (int) &console_task;task->tss.es = 1 * 8;task->tss.cs = 2 * 8;task->tss.ss = 1 * 8;task->tss.ds = 1 * 8;task->tss.fs = 1 * 8;task->tss.gs = 1 * 8;*((int *) (task->tss.esp + 4)) = (int) sht;*((int *) (task->tss.esp + 8)) = memtotal;task_run(task, 2, 2); /* level=2, priority=2 */fifo32_init(&task->fifo, 128, cons_fifo, task);return task;
}struct SHEET *open_console(struct SHTCTL *shtctl, unsigned int memtotal)
{struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;struct SHEET *sht = sheet_alloc(shtctl);unsigned char *buf = (unsigned char *) memman_alloc_4k(memman, 256 * 165);sheet_setbuf(sht, buf, 256, 165, -1); /* 无透明色 */make_window8(buf, 256, 165, "console", 0);make_textbox8(sht, 8, 28, 240, 128, COL8_000000);sht->task = open_constask(sht, memtotal);sht->flags |= 0x20; /* 有光标 */return sht;
}
到底修改了哪里呢?其实我们把之前open_console的一部分内容拿出来放到open_constask中了,正如把关闭命令行窗口的函数close_console中的一部分分离到closeconstask中一样。
void HariMain(void)
{...for (;;) {...if (fifo32_status(&fifo) == 0) {...} else {...if (256 <= i && i <= 511) { /*键盘数据*/...} else if (512 <= i && i <= 767) { /*鼠标数据*/...} else if (768 <= i && i <= 1023) { /* 命令行窗口关闭处理 */close_console(shtctl->sheets0 + (i - 768));} else if (1024 <= i && i <= 2023) {/*从此开始*/close_constask(taskctl->tasks0 + (i - 1024));/*到此结束*/}}}
}
呼,修改全部完成了,好累。
好了,我们来“make run”吧。首先试一下“ncst color”…·撒花!成功了耶!碍眼的命令行窗口没有弹出来,画面上只有应用程序的窗口。开心之余,我们又运行了color2.hrb,当然,命令行窗口还是只有一个。
咦?用鼠标点击应用程序窗口的“x”按钮无法关闭窗口!用Shift+F1强制关闭也不行。这是怎么回事!不过,按回车键总算可以正常退出了,今晚就先这样将就一下吧。
话说,今天的内容已经不少了,这个bug我们留到明天解决吧。
总结
最近写了下网络编程,这个就懈怠了,有兴趣的可以移步去看看网络编程系列哦,谢谢大家支持!