30天开发操作系统 第25天 -- 增加命令行窗口
一、增加更多颜色
1.0
到目前为止我们的操作系统只用了16种颜色,话说既然现在我们已经用上了256色的显示模式,那实际上还有240种颜色可以用,不用的话实在太浪费了。因此我们准备修改一下操作系统,以便可以显示更多的颜色。
到底要增加哪些颜色呢?我们可以为光的三原色reg、green、blue(红、绿、蓝)中每种颜 色赋予6个色阶(6个色阶:也就是6个级别的浓度的意思。在这里,定义为“6个级别的亮度” 应该更加准确。)这样一来,我们就可以定义出 6x6x6=216种颜色,没定义的就只剩下24种颜色了。
我们需要修改graphic.c,好久没碰这个文件了,真怀念啊!
void init_palette(void)
{
static unsigned char table_rgb[16 * 3] = {
...
}
unsigned char table2[216 * 3];
int r, g, b;
set_palette(0, 15, table_rgb);
for (b = 0; b < 6; b++) {
for (g = 0; g < 6; g++) {
for (r = 0; r < 6; r++) {
table2[(r + g * 6 + b * 36) * 3 + 0] = r * 51;
table2[(r + g * 6 + b * 36) * 3 + 1] = g * 51;
table2[(r + g * 6 + b * 36) * 3 + 2] = b * 51;
}
}
}
set_palette(16, 231, table2);
return;
}
这样一来,当我们需要指定RGB = [51,102,153]这个颜色时,只要使用色号137就可以了。137 这个数字的计算方法为:16+1+2×6+3×36(看set_palette,我们从16开始设定的)。
按照这样的划分方式,色号0和16所代表的颜色都是#000000,浪费了一个色号,不过我们就偷个懒先这样吧。除此之外,#f0000等颜色也会发生重复(重复的颜色一共有8种)。
接下来我们还需要编写应用程序, 不过这次不用编写新的API,所以任务很轻松。
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_initmalloc(void);
char *api_malloc(int size);
void api_refreshwin(int win, int x0, int y0, int x1, int y1);
void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
int api_getkey(int mode);
void api_end(void);
void HariMain(void)
{
char *buf;
int win, x, y, r, g, b;
api_initmalloc();
buf = api_malloc(144 * 164);
win = api_openwin(buf, 144, 164, -1, "color");
for (y = 0; y < 128; y++) {
for (x = 0; x < 128; x++) {
r = x * 2;
g = y * 2;
b = 0;
buf[(x + 8) + (y + 28) * 144] = 16 + (r / 43) + (g / 43) * 6 + (b / 43) * 36;
}
}
api_refreshwin(win, 8, 28, 136, 156);
api_getkey(1); /* 等待按下任意键 */
api_end();
}
赶紧 make run 漂亮不!
2.0
color.hrb很漂亮,不过其实还有一种技巧能够让颜色更加丰富,下面我们就来试试看吧。
怎样才能让颜色看起来更多呢?我们可以用两种颜色交替排列,看上去就像这两种颜色混合在一起一样,这就是要点。颜色的混合方式我们考虑了下面3种(算上完全不混合的情况,一共有5种)。
这样一来,虽然我们只有6级色阶,但却可以显示出21级色阶(在纯色中间可以产生3个中间色,因此是6+5x3=21)。
这次我们只需要修改应用程序即可。
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_initmalloc(void);
char *api_malloc(int size);
void api_refreshwin(int win, int x0, int y0, int x1, int y1);
void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
int api_getkey(int mode);
void api_end(void);
unsigned char rgb2pal(int r, int g, int b, int x, int y);
void HariMain(void)
{
char *buf;
int win, x, y;
api_initmalloc();
buf = api_malloc(144 * 164);
win = api_openwin(buf, 144, 164, -1, "color2");
for (y = 0; y < 128; y++) {
for (x = 0; x < 128; x++) {
buf[(x + 8) + (y + 28) * 144] = rgb2pal(x * 2, y * 2, 0, x, y);
}
}
api_refreshwin(win, 8, 28, 136, 156);
api_getkey(1); /* 等待按下任意键 */
api_end();
}
unsigned char rgb2pal(int r, int g, int b, int x, int y)
{
static int table[4] = { 3, 1, 0, 2 };
int i;
x &= 1; /* 判断是偶数还是奇数 */
y &= 1;
i = table[x + y * 2]; /* 用来生成中间色的常量 */
r = (r * 21) / 256; /* r为0-20 */
g = (g * 21) / 256;
b = (b * 21) / 256;
r = (r + i) / 4; /* r为0~5 */
g = (g + i) / 4;
b = (b + i) / 4;
return 16 + r + g * 6 + b * 36;
}
rgb2pal是本次增加颜色的核心部分,里面的算法看上去非常神秘,估计很多人都不明白为什 么如此简短的代码却能实现我们所需的功能。其实用很多个i语句也可以实现一样的功能,但相 比之下显然我们的算法速度更快,因此我们还是采用这个算法吧。
如果对解谜感兴趣的话,可以花点时间仔细思考一下。话说这个算法也不是总计想出来的, 而是一位编程达人创造的。
好,我们来 make run 吧。哦哦,看起来平滑多了。
2、窗口初始位置
在编写color.hrb的时候注意到一个小问题,color的窗口一开始出现的位置好像有点别扭 (注:使用VESA的人画面尺寸非常大,可能并不觉得有什么别扭的)。
因此我们希望让窗口总是显示在画面的中央,而且显示窗口时的图层高度也不能总是固定为 3,而是要判断当前画面中窗口的数量并自动显示在最上面。虽说现在窗口也是显示在最上面, 不过如果再多打开几个的话,情况就不一样了。
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, (shtctl->ysize - edi) / 2);
sheet_updown(sht, shtctl->top); /* 将窗口图层高度指定为当前鼠标所在图层的高度,标移到上层 */
reg[7] = (int) sht;
} else if (edx == 6) {
}
好,完工了,这下窗口应该显示在画面中央了,我们来试试看吧。 “make run” 成功了!
3、增加命令行窗口
1.0
我们中分别编写了color.hrb和color2.hrb,如果要仔细对比一下这两个程序显示出来的画面有什么区别,就要把两个程序的窗口并排起来看才行,可是我们不能同时启动两个应用程序啊。这实在是太郁闷了,我们辛辛苦苦实现的多任务到底是为了什么呢?
要解决这个问题,我们可以考虑修改一下命令行窗口,使其在应用程序运行中就可以输入下 一条命令,不过这样的修改量实在太大,讲解起 来也会很麻烦,因此我们还是改用同时启动两个 命令行窗口的方法吧。如果可以启动两个命令行窗口,就可以在每个窗口中各自启动一个应用程 序,这就相当于同时运行了两个应用程序。而命令行窗口我们一开始就是作为任务来编写的,所以要同时启动两个也很容易。
不过我们的程序中还有一部分是以只有一个命令行窗口为前提设计的,所以如果只是启动两个命令行窗口任务的话肯定是行不通的。不过我们不妨先启动两个命令行窗口试试看,如果有什么不对的地方再去一点一点地修改(一上来就想做得完美,反而会遇到麻烦的问题呢)。
于是,我们这次只修改bootpackc,将命令行窗口的相关变量(buf_cons、sht_cons、task_cons 和 cons)各准备2个,分别分给命令行1和命令行2。
例如: task_cons → taskcons1 和t ask_cons2
不过这样一来,如果要将命令行窗口增加到10个,岂不是要写10组这样的变量吗?虽说可以 复制粘贴,但还是太麻烦了,因此我们稍微动点脑筋改成下面这样就好了。
task_cons -> task_cons[0]和task_cons[1]
这样的话我们就可以用一个循环来进行相同的处理,管它10个还是100个都没问题(大家一定想到了)!
void HariMain(void)
{
...
unsigned char *buf_back, buf_mouse[256], *buf_win, *buf_cons[2];
struct SHEET *sht_back, *sht_mouse, *sht_win, *sht_cons[2];
struct TASK *task_a, *task_cons[2];
...
/* sht_cons */
for (i = 0; i < 2; i++) {
sht_cons[i] = sheet_alloc(shtctl);
buf_cons[i] = (unsigned char *) memman_alloc_4k(memman, 256 * 165);
sheet_setbuf(sht_cons[i], buf_cons[i], 256, 165, -1); /* 没有透明色 */
make_window8(buf_cons[i], 256, 165, "console", 0);
make_textbox8(sht_cons[i], 8, 28, 240, 128, COL8_000000);
task_cons[i] = task_alloc();
task_cons[i]->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 12;
task_cons[i]->tss.eip = (int) &console_task;
task_cons[i]->tss.es = 1 * 8;
task_cons[i]->tss.cs = 2 * 8;
task_cons[i]->tss.ss = 1 * 8;
task_cons[i]->tss.ds = 1 * 8;
task_cons[i]->tss.fs = 1 * 8;
task_cons[i]->tss.gs = 1 * 8;
*((int *) (task_cons[i]->tss.esp + 4)) = (int) sht_cons[i];
*((int *) (task_cons[i]->tss.esp + 8)) = memtotal;
task_run(task_cons[i], 2, 2); /* level=2, priority=2 */
sht_cons[i]->task = task_cons[i];
sht_cons[i]->flags |= 0x20; /* �J�[�\������ */
}
...
sheet_slide(sht_back, 0, 0);
sheet_slide(sht_cons[1], 56, 6);/* 这里! */
sheet_slide(sht_cons[0], 8, 2);/* 这里! */
sheet_slide(sht_win, 64, 56);
sheet_slide(sht_mouse, mx, my);
sheet_updown(sht_back, 0);
sheet_updown(sht_cons[1], 1); /*从此开始*/
sheet_updown(sht_cons[0], 2);
sheet_updown(sht_win, 3); /*到此结束*/
sheet_updown(sht_mouse, 4);
key_win = sht_win;
...
for (;;) {
...
if (256 <= i && i <= 511) { /* 键盘数据 */
...
if (i == 256 + 0x3b && key_shift != 0 && task_cons[0]->tss.ss0 != 0) { /* Shift+F1 */
cons = (struct CONSOLE *) *((int *) 0x0fec);
cons_putstr0(cons, "\nBreak(key) :\n");
io_cli();
task_cons[0]->tss.eax = (int) &(task_cons[0]->tss.esp0);
task_cons[0]->tss.eip = (int) asm_end_app;
io_sti();
}
...
} else if (512 <= i && i <= 767) { /* 鼠标数据 */
...
if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) {
/* 点击“X”按钮 */
if ((sht->flags & 0x10) != 0) { /* 应用窗口 */
cons = (struct CONSOLE *) *((int *) 0x0fec);
cons_putstr0(cons, "\nBreak(mouse) :\n");
io_cli(); /* 强制结束处理时禁止任务切换 */
task_cons[0]->tss.eax = (int) &(task_cons[0]->tss.esp0);/* 这里 */
task_cons[0]->tss.eip = (int) asm_end_app; /* 这里 */
io_sti();
}
}
break;
}
}
}
}
}
前半部分代码应该还很容易看懂,不过后半部分Shif+FI以及“x”按钮的处理是不是觉得有些蹊跷?为什么不判断就直接写cons[0]呢?其实呢,这里纯粹是偷懒了(笑)。如果我们不加上[0]或者[1]的话编译器通不过,因此就先写了[0],关于这一块,我们会在后面进行修复。
因此,在运行harib22e和harib22f的时候,大家记得千万别按Shif+F1或者“X”按钮哦。
我们来 make run ,目前打开两个命令行窗口应该没问题吧。出来吧!
在两个窗口之间切换一下,貌似都能够执行命令。之所以说“貌似”,是因为我们知道剩下的问题还有很多,至于后面到底能不能顺利运行,说实话这次真没什么自信。
那么现在到底能不能同时启动两个应用程序呢?虽然很想用color.hrb和color2.hrb来试一下,不过大抵是不会成功的,所以我们还是从基本中的基本 – a.hrb开始测试吧。喷?什么都没显示出来???…晕!在这儿呢!居然显示到没运行这个程序的命令行窗口中去了。
这样可不行啊,我们得解决这个问题。
2.0
到底应该改哪里呢?其实大概心里有数了,函数hrb_api0中的这句:
struct CONSOLE *cons= (struct CONSOLE *) *((int *) 0x0fec);
“要向哪个命令行窗口输出字符”的关键应该就是问题所在了,这里的cons变量是用来判断。该变量的值是从内存地址0x0fec读取出来的,而无论从哪个任务读取这个内存地址中的值, 得到的肯定都是同一个值,因此不管在哪个窗口中运行a.hrb,都只能在固定的其中一个窗口中显示字符。
那么该如何解决这个问题呢?嗯,看看这种方法怎么样。
struct TASK {
int sel, flags; /* sel表示GDT编号 */
int level, priority;
struct FIFO32 fifo;
struct TSS32 tss;
struct CONSOLE *cons; /*从此开始*/
int ds_base; /*到此结束*/
};
每个任务都拥有各自的TASK结构,只要我们将cons保存在TASK结构中,就可以由不同的任务读取出不同的值了。此外,我们将ds_base也放到了TASK结构中,理由和上面的cons是相同的。ds_ base之前是从内存地址0x0fe8处读取的,但很明显,cons[0]的应用程序数据段地址和cons[1] 的地址肯定是不同的,如果不在这里区分开的话,字符串的显示就会出问题。
接下来我们只要将代码中的*((int*) 0x0fec)和*((int*) 0x0fe8)全部改为使用TASK结构中的 cons 和 ds_base 成员就可以了,需要修改的只有console.c一个文件。
void console_task(struct SHEET *sheet, int memtotal)
{
...
task->cons = &cons; /*修改前:*((int*)0xOfec)=(int)&cons;*/
...
}
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
...
if (finfo != 0) {
...
if (finfo->size >= 36 && strncmp(p + 4, "Hari", 4) == 0 && *p == 0x00) {
...
task->ds_base = (int) q; /*修改前:*((int *)0xOfe8)=(int)&q;*/
...
}
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
...
int ds_base = task->ds_base; /*这里!*/
struct CONSOLE *cons = task->cons; /*这里!*/
...
}
int *inthandler0c(int *esp)
{
...
struct CONSOLE *cons = task->cons; /*这里!*/
...
}
int *inthandler0d(int *esp)
{
...
struct CONSOLE *cons = task->cons; /*这里!*/
...
}
好,完工啦。现在无论在哪个窗口运行a.hrb,应该会在相应的窗口中显示出字符………了吧…
我们来试验一下吧。 “make run”,在两边的窗口分别运行a.hrb!哦哦,成功了,太棒了!(要是这样还不行的话,真的不知道该怎么办了,小捏了一把汗)
趁热打铁,我们来运行color.hrb和color2.hrb试试看。哇!成功了!现在我们可以把两个窗口并排对比了,终于实现了当初的夙愿。
接下来我们来做点别的试验吧,比如说把color.hrb和color2.hrb的窗口关闭(注:Shif+F1和 “x”按钮点击的处理我们还没有改好,记得按回车键退出程序哦),QEMU出错退出了!
因此,在两个color程序PK之后,我们的下一个目标就是要使窗口能够正常关闭。
3.0
为什么程序会无法正常关闭呢?一开始以为问题出在关闭窗口的函数,或者是处理程序结束的部分,但事实并非如此,这个失误比想象中更加严重。问题的原因在于应用程序的内存段消失了,突然间竟然发生这种事情,QEMU肯定也被整糊涂了。
也许大家不明白应用程序的内存段消失是怎么一回事,总之,问题出在cmd_app身上。
set_segrdesc (gdt + 1003, finfo->size- 1, (int)p, AR_CODE32_ER+0x60);
set_segmdesc (gat + 1004, segsiz- 1, (int)q, ARLDATA32_RW+0x60);
(中略)
start_app(0x1b, 1003 * 8, esp,1004 *8, &(task->tss.esp0));
上面这段代码是用来创建应用程序段并启动应用程序的,大家仔细思考一下这段代码。
首先,color.hrb在某个窗口中被运行,启动程序一切顺利,然后显示窗口并绘图,接下来等待键盘输入并进入休眠状态。到这里为止没有任何问题。
然后我们在另外一个窗口中运行color.hrb,程序也顺利启动了,显示窗口并绘图,随后进入休眠状态。然而在这个时候,问题其实已经发生 了。这是怎么回事呢?因为我们为color.hrb准备的1003号代码段和1004号数据段,被color2.hrb所用的段给覆盖掉了。
当按下回车键唤醒color.hrb时,因此,就会发生异常情况—明明应该去运行color.hrb的,结果却错误地运行了color2.hrb, 这样当然会出错了。
既然问题的原因想明白了,要干掉这个bug也就不难了,只要为color.hrb和color2.hrb分配编号不同的段就可以了。
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
...
if (finfo != 0) {
...
if (finfo->size >= 36 && strncmp(p + 4, "Hari", 4) == 0 && *p == 0x00) {
...
set_segmdesc(gdt + task->sel / 8 + 1000, finfo->size - 1, (int) p, AR_CODE32_ER + 0x60);
set_segmdesc(gdt + task->sel / 8 + 2000, segsiz - 1, (int) q, AR_DATA32_RW + 0x60);
for (i = 0; i < datsiz; i++) {
q[esp + i] = p[dathrb + i];
}
start_app(0x1b, task->sel + 1000 * 8, esp, task->sel + 2000 * 8, &(task->tss.esp0));
...
}
在task->sel中填人TSS的段号*8 (请参照mtask.c的task_init),将这个值除以8,结果一定落在3~1002。将其加上1000,就得到1003 ~ 2002的值,我们把它用作应用程序用的代码段编号;将其加上2000,即得到2003 ~ 3002的值,我们把它用作应用程序用的数据段编号。这样一来,就不会发生段被覆盖的问题了。
我们来试试看能不能成功,运行color.hrb和color2.hrb,并排对比一下,然后按下回车键结束程序。啊,这次终于成功了!
4.0
增加命令行窗口这个系列终于到了最后一节,之前我们已经知道Shif+F1和 “x” 按钮的部分是有问题的,但一直放着没管,现在终于到了解决它的时候了。
void HariMain(void)
{
...
struct TASK *task_a, *task_cons[2], *task;
...
for (;;) {
...
if (256 <= i && i <= 511) {
...
/*从此开始*/
if (i == 256 + 0x3b && key_shift != 0) {
task = key_win->task;
if (task != 0 && task->tss.ss0 != 0) { /* Shift+F1 */
cons_putstr0(task->cons, "\nBreak(key) :\n");
io_cli(); /* 强制结束处理时禁止任务切换 */
task->tss.eax = (int) &(task->tss.esp0);
task->tss.eip = (int) asm_end_app;
io_sti();
}
}/*到此结束*/
...
} else if (512 <= i && i <= 767) { /*鼠标数据*/
...
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();
}
}
break;
}
...
}
这次的修改也很简单,首先将原来task_cons[0]的地方改为key_win->task和sht->task,这样一来,用键盘强制结束时会以当前输入窗口为对象,而用鼠标点击“x”按钮时会以被点击的窗口为对象。然后,我们将从内存地址0xfec读出cons的部分改为使用task->cons,这样就改好了。
我们来“make run",将color.hrb和color2.hrb的窗口并排显示之后,用键盘或者鼠标强制结束试试………看,成功了!
4、变得更像操作系统
1.0
做到这里,我们的系统看上去已经非常像那么回事了。不过,大家回忆一下一般操作系统的样子,再看看我们的画面,是不是有什么奇怪的东西混进去了?一般的操作系统中哪有这玩意儿啊?所说的,就是task_a的窗口。
一般的操作系统不会一上来就弹出这么一个窗口来吧?话说回来,这个窗口貌似是14天中我们为了让画面看上去更酷更有操作系统范儿而做出来的,也就是说,它只是个花瓶,摆着好看罢了。之后,我们在练习多任务等部分时这个窗口也还派上过用场,不过现在它看起来确实有点碍手碍脚了 – 它不是应用程序而是操作系统的一部分,所以连关都关掉。
因此,我们还是把它从操作系统中删掉吧,如果哪天我们又想念它了,只要再写一个应用程 序出来就好了。而且应用程序的话,不需要的时候就可以关掉,(如果需要)还可以同时运行多个副本呢。
其实, 在bootpack.c中,只有这个taska的窗口是搞特殊化的,如果删掉task_a的部分程序就变得清爽多了。对了,task_a的处理全部是在】bootpack.c中完成的,因此不需要修改其他的程序。
void HariMain(void)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
struct SHTCTL *shtctl;
char s[40];
struct FIFO32 fifo, keycmd;
int fifobuf[128], keycmd_buf[32];
int mx, my, i;
unsigned int memtotal;
struct MOUSE_DEC mdec;
struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
unsigned char *buf_back, buf_mouse[256], *buf_cons[2];
struct SHEET *sht_back, *sht_mouse, *sht_cons[2];
struct TASK *task_a, *task_cons[2], *task;
static char keytable0[0x80] = {
...
/*删掉了关于sht_win的部分*/
sheet_slide(sht_back, 0, 0);
sheet_slide(sht_cons[1], 56, 6);
sheet_slide(sht_cons[0], 8, 2);
/*删掉了sht_win */
sheet_slide(sht_mouse, mx, my);
sheet_updown(sht_back, 0);
sheet_updown(sht_cons[1], 1);
sheet_updown(sht_cons[0], 2);
sheet_updown(sht_mouse, 3); /*从此开始*/
key_win = sht_cons[0];
keywin_on(key_win); /*到此结束*/
...
for (;;) {
...
if (256 <= i && i <= 511) {
...
/*从此开始*/
if (s[0] != 0) { /* 一般字符、退格键、回车键 */
fifo32_put(&key_win->task->fifo, s[0] + 256);
}
if (i == 256 + 0x0f) { /* Tab */
keywin_off(key_win);
j = key_win->height - 1;
if (j == 0) {
j = shtctl->top - 1;
}
key_win = shtctl->sheets[j];
keywin_on(key_win);
}/*到此结束*/
...
/*删掉了“重新显示光标”的部分*/
} else if (512 <= i && i <= 767) {
...
if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) {
if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {
sheet_updown(sht, shtctl->top - 1);
if (sht != key_win) {
keywin_off(key_win); /*这里!*/
key_win = sht;
keywin_on(key_win); /*这里!*/
}
/*删掉了“光标用定时器”的部分*/
...
}
void keywin_off(struct SHEET *key_win)
{
change_wtitle8(key_win, 0);
if ((key_win->flags & 0x20) != 0) {
fifo32_put(&key_win->task->fifo, 3); /* 命令行窗口光标OFF */
}
return;
}
void keywin_on(struct SHEET *key_win)
{
change_wtitle8(key_win, 1);
if ((key_win->flags & 0x20) != 0) {
fifo32_put(&key_win->task->fifo, 2); /* 命令行窗口光标ON */
}
return;
}
看上去清爽多了。修改前的bootpack.c一共有374行代码,修改后只剩下304行大功告成,居然减掉了70行。
我们还是来 make run 吧,能不能成功呢。哎呀?
看来是失败了… 貌似系统在不断重启,当然,任何操作都没有反应…
2.0
当然不能就这样放弃,我们来找找失败的原因。在对有可能出问题的地方排查一遍之后,我们发现只要将bootpack.c的第117行keywin_ on(key. win);这一句删掉”,重启的毛病就没了。不过这样一来,启动之后光标没有显示出来,必须要按Tab键或者点击窗口将窗口手工切换一下才行。
/*删掉了sht_win */
sheet_slide(sht_mouse, mx, my);
sheet_updown(sht_back, 0);
sheet_updown(sht_cons[1], 1);
sheet_updown(sht_cons[0], 2);
sheet_updown(sht_mouse, 3);
key_win = sht_cons[0];
keywin_on(key_win); /*将这一行删掉的话,重启的毛病就没了*/
看来, 只要检查一下keywin_on函数就能找到自动重启的原因了。
void keywin_on(struct SHEET *key_win)
{
change_wtitle8(key_win, 1);
if ((key_win->flags & 0x20) != 0) {
fifo32_put(&key_win->task->fifo, 2); /* 命令行窗口光标ON */
}
return;
}
keywin_on这个函数的功能很简单,先是改变窗口的颜色,然后向命令行窗口任务的FIFO发送2这个值用来显示光标。我们将刚才出问题的keywin_ on(key_win);这一句改成 change_wtitle8(key_ win, 1);试了一下,没有发生重启的问题,看来问题的原因就在fifo32put这里了。
不过,fifo32put这个函数我们已经用了很久了,从来没有出过问题,应该不会是程序的bug,因此我们来检查下发送数据的目的地key_win->task-> fifo。
这个FIFO缓冲区是否处于正常状态呢?想到这里,我们来找找看对这个FIFO缓冲区进行初始化的代码在哪里,啊,找到了,位于console_task最开头的地方。
这样肯定不行,因为命令行窗口任务的优先级比较低,只有当bootpack.c的HariMain休眠之后才会运行命令行窗口任务,而如果不运行这个任务的话,FIFO缓冲区就不会被初始化,这就相当于我们在向一个还没初始化的FIFO强行发送数据,于是造成ffo32_put混乱而导致重启。
搞清楚原因,改起来就简单了。我们只要将console_task中对FIFO进行初始化的代码移动到 HariMain中就可以了。
void console_task(struct SHEET *sheet, int memtotal)
{
struct TASK *task = task_now();
struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
int i, *fat = (int *) memman_alloc_4k(memman, 4 * 2880);
struct CONSOLE cons;
char cmdline[30];
cons.sht = sheet;
cons.cur_x = 8;
cons.cur_y = 28;
cons.cur_c = -1;
task->cons = &cons;
cons.timer = timer_alloc();
timer_init(cons.timer, &task->fifo, 1);
timer_settime(cons.timer, 50);
file_readfat(fat, (unsigned char *) (ADR_DISKIMG + 0x000200));
...
}
虽然我们只修改了5行代码,但是完全可以解决我们的问题,话说,要是还解决不了可真该头大了,因为上眼皮和下眼皮已经开始打架了(笑)。总之,我们来“make run”试试看吧。
貌似成功了,那个陪伴我们很久的task_a已经不见了, 系统运行也没有问题,太好喽。
总结
大家周末过得怎么样,明天就要上班子了哦!