自学内容网 自学内容网

【读书笔记-《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按钮关闭。这个问题留待下一篇解决,敬请期待。


原文地址:https://blog.csdn.net/Ocean1994/article/details/142497818

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!