【读书笔记-《30天自制操作系统》-25】Day26
本篇仍然是围绕着命令行窗口做文章。首先优化命令行窗口的移动速度,然后增加多个命令行窗口功能。接着优化了命令行窗口的关闭,最后增加了两个命令start与ncst。
1. 优化命令行窗口移动速度
首先对命令行窗口的移动速度进行优化。主要的优化点有以下几个:
(1) sheet_refreshmap函数中过多的不必要的if语句
sheet_refreshmap函数相关代码如下:
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;unsigned char *buf, *vram = ctl->vram, *map = ctl->map, sid;struct SHEET *sht;if (vx0 < 0) { vx0 = 0; }if (vy0 < 0) { vy0 = 0; }if (vx1 > ctl->xsize) { vx1 = ctl->xsize; }if (vy1 > ctl->ysize) { vy1 = ctl->ysize; }for (h = h0; h <= h1; h++) {sht = ctl->sheets[h];buf = sht->buf;sid = sht - ctl->sheets0;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语句if (map[vy * ctl->xsize + vx] == sid) {vram[vy * ctl->xsize + vx] = buf[by * sht->bxsize + bx];}}}}return;
}
可以看出其中一条if语句位于三层循环之中,会执行成千上万次,如果能够去掉的话,速度应该会有不小的提升。
这个语句的作用是判断图层是否为透明部分,这主要是对于鼠标来说的。鼠标的显示图层整体是一个矩形,箭头之外的部分是透明的,这样显示出来才是一个箭头;而对于窗口等其他图层来说,不存在透明的部分。因此我们在进入循环之前可以先判断图层是否有透明部分,如果没有的话就不需要执行if语句了。
if (sht->col_inv == -1) {/* 无透明图层,可以去掉if语句提高速度 */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 {/* 有透明图层,仍需要执行if语句 */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;}}}}
在QEMU下运行可能没有太明显的感觉,但是这种逻辑上的修改肯定是更为优化的。
(2) 将一次写入一个字节的MOV指令替换为一次写入四个字节的MOV指令
上面的代码中有一句:
map[vy * ctl->xsize + vx] = sid;
其作用是向某个内存地址写入sid的值。这条语句位于for循环中,后面的很多内存地址都要执行同样的操作。如果将这样一次写入一个字节替换为一次写入4个字节,执行一条指令的时间仍然是一样的,这样写入的速度就大大加快了。
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;}}}
……
写入的字节数为4个,我们需要使窗口在x方向上的大小以及窗口的x坐标也为4的倍数。目前窗口大小都是4的倍数,而对于窗口坐标,需要通过AND来取整,使打开窗口显示的位置为4的倍数。
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);/* 取整为4的倍数 */sheet_updown(sht, shtctl->top); reg[7] = (int) sht;}
当然,移动之后的窗口坐标也需要使4的倍数:
else
{/* 鼠标处于移动模式 */x = mx - mmx; /* 计算鼠标移动量 */y = my - mmy;sheet_slide(sht, (mmx2 + x + 2) & ~3, sht->vy0 + y);/* 取整为4的倍数*/mmy = my;
}
(3) QEMU中运行时,窗口移动的速度仍然赶不上鼠标移动的速度
因为图层移动需要进行的绘图操作非常耗时,导致操作系统来不及处理FIFO中的鼠标移动数据,这样就会出现放开鼠标键窗口还在移动的现象。优化为在接收到鼠标移动数据后不立即进行绘图操作,而等到FIFO为空时再进行绘图操作。
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) { /* マウスデータ */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_my将移动后的坐标暂时保存起来,在FIFO为空时再执行sheet_slide(sht_mouse, new_mx, new_my)更新鼠标的位置。而当放开鼠标左键退出窗口移动模式时,用户可能马上会去移动其他窗口,因此这里即使FIFO不为空也要立即更新窗口的位置。
2. 增加任意多个命令行窗口
在操作系统中可以根据需要打开多个命令行窗口,这里我们增加通过Shift+F2的按键来打开新的命令行窗口的命令。
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);
}
3. 优化命令行窗口关闭
打开的窗口多了,我们还要考虑关闭的问题。
首先是通过在命令行窗口中输入exit命令来关闭。
关闭一个命令行窗口,我们需要将创建时的内存空间以及窗口的图层和任务结构全部释放。但之前我们为命令行窗口准备了专用的栈,却没有将栈地址保存,这样就无法释放这些内存。所以我们需要在TASK结构中添加一个cons_stack成员保存栈地址:
struct TASK {int sel, flags; int level, priority;struct FIFO32 fifo;struct TSS32 tss;struct CONSOLE *cons;int ds_base, cons_stack;
};
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_consoletask中使任务进入休眠状态。这样任务就被从等待切换的列表中移除,绝对不会再切换到该任务,就可以安全地释放栈与FIFO缓冲区了。为了使task_alloc还能使用这些空间,需要将flags置为0。
exit命令的实现如下:
……
else if (strcmp(cmdline, "exit") == 0) {cmd_exit(cons, fat);}
……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);}
}
这里如果在cmd_exit函数中直接调用close_console,则相当于对自身任务执行了休眠,这样就无法继续执行其他程序了。因此这里通过给任务A发消息来让任务A调用close_console。发送给任务A之后,当前任务再进入休眠就没问题了。
在任务A中,需要增加对关闭命令行窗口命令的响应:
……
else if (768 <= i && i <= 1023)
{ /* 命令行窗口关闭处理 */close_console(shtctl->sheets0 + (i - 768));
}
……
此外,之前还没有出现过画面上完全没有窗口的情况,也需要进行处理:
……
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);}
}
当画面上没有窗口时,我们将key_win置为0,与通常情况进行区别。
接下来继续实现通过鼠标关闭命令行窗口,只需要增加简单的处理。主程序中增加的处理:
if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) {/* 点击「×」按钮 */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();}}
在console_task中增加以下处理:
……for (;;) {io_cli();if (fifo32_status(&task->fifo) == 0) {task_sleep(task);io_sti();} else {i = fifo32_get(&task->fifo);io_sti();if (i <= 1) {……if (i == 4) { /* 点击命令行窗口的x按钮 */cmd_exit(&cons, fat);}……}
4. 增加start与nsct命令
当前我们要运行一个程序,需要在命令行窗口中输入对应的指令。下面来开发start命令,用于打开一个新的命令行的窗口并直接运行相应的应用程序。
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;
}
运行效果如下:
这样可以直接通过start运行一个程序。但是这里还需要额外打开一个命令行窗口。有些应用程序不需要在命令行窗口显示,我们又想要直接运行应用程序,而不额外打开一个命令行窗口,因此开发一个ncst(no console start)命令。
首先我们将没有窗口的命令行任务的cons->sht规定为0。在没有窗口的情况下执行dir与cls等命令没有效果,因此将这些屏蔽掉。cmd_ncst先按照cmd_start的样子去写。
……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);
……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;
}
当cons->sht为0时,需要禁用命令行窗口的字符显示等操作,因此字符显示部分需要增加如下判断条件:
if (cons->sht != 0)
{putfonts8_asc_sht(cons->sht, cons->cur_x, cons->cur_y, COL8_FFFFFF, COL8_000000, " ", 1);
}
对于console_task,需要做如下修改:在不显示命令行窗口时,需要禁用一些不必要的处理,与上面类似;命令执行完还要立即结束命令行窗口任务,否则无法继续输入其他命令。
……
if (sheet != 0) {cons.timer = timer_alloc();timer_init(cons.timer, &task->fifo, 1);timer_settime(cons.timer, 50);
}
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);
}
cmd_exit则需要增加无命令行窗口的任务结束处理。
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);}
}
在没有命令行窗口时,需要提供TASK结构的地址用于结束任务。
然后是cmd_ncst中调用的open_constask函数:
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;
}
最后在主程序中再增加一些代码就可以了:
……
else if (1024 <= i && i <= 2023)
{close_constask(taskctl->tasks0 + (i - 1024));
}
……
这样在命令行窗口中可以通过ncst命令直接运行一个应用程序:
不过还存在一点问题,这里打开的应用程序窗口不能通过x按钮关闭。这个问题留待下一篇解决,敬请期待。