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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值