30天开发操作系统 第24天 -- 窗口操作

news2025/2/28 10:40:34

一、窗口切换

1.0

前天开始我们的应用程序可以显示自己的窗口了,现在画面上到处都是窗口,我们急需能够 切换窗口顺序的功能,使得在需要的时候可以查 看最下面的窗口的内容。这个功能看起来不难,我们马上来实现它。 不过,一上来就实现“点击鼠标切换窗口”的功能还有点难,所以我们先从用键盘切换的方法入手吧,即按下F11时,将最下面的那个窗口放到最上面。
要实现这一功能我们首先需要知道F11的按键编码:F11的按键编码为0x57(F12为0x58)。知道了这些,接下来只要稍微修改下bootpack.c即可 ,只需要添加3行代码。

void HariMain(void)
{
	...
	if (i == 256 + 0x57 && shtctl->top > 2) {	/* F11 */
					sheet_updown(shtctl->sheets[1], shtctl->top - 1);
				}
	...
}

恐怕没大家都记不清图层的操作方法了吧,我们还是稍微详细地讲解一下。 sheet_updown (shtct1->sheets [1],shtct1->top -1); 这句代码的功能是将从下面数第2个图层(最下面一个图层shtctl->sheets[O]是背景)的高度 提升为shtctl →>top- 1。 Shtctl ->top这个高度存放的是最上面一个图层的高度,这个图层永远是绘制鼠标指针用的,我们不能将窗口放在比鼠标还高的位置上(搞个恶作剧倒是挺有趣的),因此将窗口高度设置为鼠标图层的下面一层。
我们来试试看能不能成功,-- “make run" – 运行成功了!
在这里插入图片描述

2.0

这次我们来实现Windows那样的用鼠标点击来切换窗口的功能。这个功能会让task_a中窗口移动的功能失效,不过没关系,我们在下一节会重新编写窗口移动功能的,大家不必担心。 当鼠标点击画面的某个地方时,怎样才能知道鼠标所点击到的是哪个图层呢?
我们需要按照从上到下的顺序,判断鼠标的位置落在哪个图层的范围内,并且还需要确保该位置不是透明色区域。

void HariMain(void)
{
	int j, x, y;	/*这里!*/
	struct SHEET *sht;	/*这里!*/
	...
	for (;;) {
		...
		if ((mdec.btn & 0x01) != 0) {
							/* 按下左键 */
							/* 按照从上到下的顺序寻找鼠标所指向的图层 */
							/*从此开始*/
							for (j = shtctl->top - 1; j > 0; j--) {
								sht = shtctl->sheets[j];
								x = mx - sht->vx0;
								y = my - sht->vy0;
								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);
										break;
									}
								}
			/*到此结束*/		}
						}
					}
		...
}

make run 然后运行lines.hrb, 我们来点击一个窗口试试看。哦!成功了!
在这里插入图片描述
如果点击命令行窗口将其切换到最上面的话,别的窗口就全都被遮住了,这样无法用鼠标点击切换了,还好我们可以按F11将被遮住的窗口切换出来,F11这个功能还真是挺方便的呢。

2 移动窗口

窗口切换的功能已经做的差不多了,这次我们来实现窗口的移动。之前我们只能移动task_ a的窗口,这次的目标是实现像Windows一样的窗口移动功能。不过要如何才能实现呢? 当鼠标左键点击窗口时,如果点击位置位于窗口的标题栏区域,则进入“窗口移动模式” 使窗口的位置追随鼠标指针的移动,当放开鼠标左键时,退出“窗口移动模式”,返回通常模式。 要实现窗口的移动,我们需要记录鼠标指针所移动的距离,为此我们添加了两个变量:mmx和mmy,这两个变量所记录的是移动之前的坐标。由于鼠标指针mm是“move mode”的缩写,因此我们规定当mmx为负数时代表当前不处于窗口移动模式。 由于鼠标不会跑到画面以外,因此我们规定当mmx为负数时代表当前不处于窗口移动模式。

void HariMain(void)
{
	...
	int j, x, y, mmx = -1, mmy = -1;
	...
	for (;;) {
		...
		/*从此开始*/
		if ((mdec.btn & 0x01) != 0) {
							/* 按下左键 */
							if (mmx < 0) {
								/* 如果处于通常模式 */
								/* 按照从上到下的顺序寻找鼠标所指向的图层 */
								for (j = shtctl->top - 1; j > 0; j--) {
									sht = shtctl->sheets[j];
									x = mx - sht->vx0;
									y = my - sht->vy0;
									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 (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) {
												mmx = mx;	/* 进入窗口移动模式 */
												mmy = my;
											}
											break;
										}
									}
								}
							} else {
								/* 如果处于窗口移动模式 */
								x = mx - mmx;	/* 计算鼠标的移动距离 */
								y = my - mmy;
								sheet_slide(sht, sht->vx0 + x, sht->vy0 + y);
								mmx = mx;	/* 更新为移动后的坐标 */
								mmy = my;
							}
						} else {
							/* 没有按下左键 */
							mmx = -1;	/* 返回通常模式 */
						}
					}
}

虽然代码有点长,不过静下心来仔细读的话,大家一定能看懂的。 我们来 make run 试试看,能不能成功呢……成功了! 不过命令行窗口的移动速度太慢了!这大概是QEMU的原因,在真机环境下窗口的移动一定可以达到可接受的速度。

3 用鼠标关闭窗口

现在我们已经实现了窗口的移动,这次就来实现关闭窗口的功能吧。我们的窗口上面已经有 了一个“x”按钮,看到它大家都想按一下吧(笑)? 判断是否点击了“x”按钮的方法,和之前窗口移动时判断是否点击到标题栏的方法是一样的,而且点击后的程序结束处理,也可以参考强制结束部分的代码。嗯,这样看上去还挺容易的。 我们添加了11行代码,这样应该可以搞定了。

void HariMain(void)
{
	...
	for (;;) {
	...
		if ((mdec.btn & 0x01) != 0) {
		...
			if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) {
													/*点击“X”按钮*/
													if (sht->task != 0) {	/*该窗口是否为应用程序窗口*/
														cons = (struct CONSOLE *) *((int *) 0x0fec);
														cons_putstr0(cons, "\nBreak(mouse) :\n");
														io_cli();	/*强制结束处理中禁止切换任务*/
														task_cons->tss.eax = (int) &(task_cons->tss.esp0);
														task_cons->tss.eip = (int) asm_end_app;
														io_sti();
													}
												}
												break;
	...
}

将上面的程序“make run” 结果如下:
在这里插入图片描述

4 将输入切换到应用程序窗口

虽然我们已经实现了像walk.hrb这样让应用程序接受键盘输入的功能,不过仔细看画面会发现,处于输入状态的其实是命令行窗口,而不是walk的窗口。
在这里插入图片描述
这样看上去有点怪,应该先让应用程序窗口处于输入状态,然后再操作“*” 进行移动。 之前我们所使用的Tab键切换很简单,只能在两个窗口之间交替进行,但这次我们已经显示出了3个以上的窗口,情况变得有点复杂,在按下Tab键时,我们需要判断切换到哪个窗口。
我们先这样规定吧:按下Tab键时将键盘输入切换到当前输入窗口下面一层的窗口中,若当前窗口为 最下层,则切换到最上层窗口。 对于键盘输入的控制我们之前用的是key_to这个变量,不过这已经是很久以前写的了(应该但感觉好像已经过了很久了呢),这个方法是在17天中写的,现在已经不能用了,于是我们改成使用key_win这个变量存放当前处于输人模式的窗口地址。
另外,还有一个问题,如果当应用程序窗口处于输入模式时被关闭的话要怎样处理呢?这个时候我们可以让系统自动切换到最上层的窗口。 本次修改的内容较多。主要修改的地方是将HariMain中key_to的地方改为key_win,以及添加在输入窗口消失时(被关闭时)进行处理的代码。
有个细节需要讲一下,我们用SHEET结构中的task成员来判断数据发送对象的FIFO,因此在 sht cons->task中也加入了TASK结构的地址,这样的话我们就无法分辨窗口是不是由应用程序生 成的,于是我们需要通过SHEET结构中的flags成员进行判断(以0x10比特位进行区分)。此外, 只有命令行窗口需要控制光标的ON/OFF,应用程序窗口不需要,这一区别也是通过flags来进行 判断的(以0x20比特位进行区分)。

void HariMain(void)
{
	...
	struct SHEET *sht = 0, *key_win;	/*这里!*/
	...
	/*从此开始*/
	key_win = sht_win;
	sht_cons->task = task_cons;
	sht_cons->flags |= 0x20;	/*有光标*/
/*到此结束*/
	if (key_win->flags == 0) {	/* 输入窗口被关闭 */
				key_win = shtctl->sheets[shtctl->top - 1];
				cursor_c = keywin_on(key_win, sht_win, cursor_c);
			}
	...
	else {
			i = fifo32_get(&fifo);
			io_sti();
			if (key_win->flags == 0) {	/* 输入窗口被关闭 */ /*这里!*/
				key_win = shtctl->sheets[shtctl->top - 1];
				cursor_c = keywin_on(key_win, sht_win, cursor_c);
			}
			if (256 <= i && i <= 511) { /* 键盘数据 */
				if (i < 0x80 + 256) { 
					if (key_shift == 0) {
						s[0] = keytable0[i - 256];
					} else {
						s[0] = keytable1[i - 256];
					}
				} else {
					s[0] = 0;
				}
				if ('A' <= s[0] && s[0] <= 'Z') {	
					if (((key_leds & 4) == 0 && key_shift == 0) ||
							((key_leds & 4) != 0 && key_shift != 0)) {
						s[0] += 0x20;	
					}
				}
				if (s[0] != 0) { /* 一般字符 */
					if (key_win == sht_win) {	/* 发送至任务A */
						if (cursor_x < 128) {
							/* 显示一个字符并将光标后移一位 */
							s[1] = 0;
							putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1);
							cursor_x += 8;
						}
					} else {	/* 发送至命令行窗口 */
						fifo32_put(&key_win->task->fifo, s[0] + 256);
					}
				}
				if (i == 256 + 0x0e) {	/* 退格键 */
					if (key_win == sht_win) {	/* 发送至任务A */
						if (cursor_x > 8) {
							/* 用空格擦除光标后将光标前移一位 */
							putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1);
							cursor_x -= 8;
						}
					} else {	/* 发送至命令行窗口 */
						fifo32_put(&key_win->task->fifo, 8 + 256);
					}
				}
				if (i == 256 + 0x1c) {	/* Enter */
					if (key_win != sht_win) {	/* 发送至命令行窗口 */
						fifo32_put(&key_win->task->fifo, 10 + 256);
					}
				}
				if (i == 256 + 0x0f) {	/* Tab */
					cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x);
					j = key_win->height - 1;
					if (j == 0) {
						j = shtctl->top - 1;
					}
					key_win = shtctl->sheets[j];
					cursor_c = keywin_on(key_win, sht_win, cursor_c);
				}/*到此结束*/
				...
				else if (512 <= i && i <= 767) { /* 鼠标数据 */
				if (mouse_decode(&mdec, i - 512) != 0) {
					mx += mdec.x;
					my += mdec.y;
					if (mx < 0) {
						mx = 0;
					}
					if (my < 0) {
						my = 0;
					}
					if (mx > binfo->scrnx - 1) {
						mx = binfo->scrnx - 1;
					}
					if (my > binfo->scrny - 1) {
						my = binfo->scrny - 1;
					}
					sheet_slide(sht_mouse, mx, my);
					if ((mdec.btn & 0x01) != 0) {
						/* 按下左键 */
						if (mmx < 0) {
							/* 如果处于通常模式 */
							/* 按照从上到下的顺序寻找鼠标所指向的图层 */
							for (j = shtctl->top - 1; j > 0; j--) {
								sht = shtctl->sheets[j];
								x = mx - sht->vx0;
								y = my - sht->vy0;
								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 (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) {
											mmx = mx;	
											mmy = my;
										}
										if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) {
											/*点击“X”按钮*/
											/*这里!*/if ((sht->flags & 0x10) != 0) {		/*是由应用程序生成的窗口*/
									
}

上面的代码中调用了keywin_on和keywin_off两个函数,它们的功能是控制窗口标题栏的颜色 和task_a窗口的光标,我们将它们写在bootpack.c中。

int keywin_off(struct SHEET *key_win, struct SHEET *sht_win, int cur_c, int cur_x)
{
	change_wtitle8(key_win, 0);
	if (key_win == sht_win) {
		cur_c = -1; /* 删除光标 */
		boxfill8(sht_win->buf, sht_win->bxsize, COL8_FFFFFF, cur_x, 28, cur_x + 7, 43);
	} else {
		if ((key_win->flags & 0x20) != 0) {
			fifo32_put(&key_win->task->fifo, 3); /* 命令行窗口光标OFF */
		}
	}
	return cur_c;
}

int keywin_on(struct SHEET *key_win, struct SHEET *sht_win, int cur_c)
{
	change_wtitle8(key_win, 1);
	if (key_win == sht_win) {
		cur_c = COL8_000000; /* 显示光标 */
	} else {
		if ((key_win->flags & 0x20) != 0) {
			fifo32_put(&key_win->task->fifo, 2); /* 命令行窗口光标ON */
		}
	}
	return cur_c;
}

上面的代码中我们调用了一个叫做change_wtitle8的函数,这个函数的功能是改变窗口标题栏 的颜色,我们写在window.c中。其实make_wtitle8也可以实现相同的功能,但change_wtitle8的好处是,即便不知道窗口的名称也可以改变标题栏 的颜色。除此之外,代码的其余部分应该不需要讲解了吧。

void change_wtitle8(struct SHEET *sht, char act)
{
	int x, y, xsize = sht->bxsize;
	char c, tc_new, tbc_new, tc_old, tbc_old, *buf = sht->buf;
	if (act != 0) {
		tc_new  = COL8_FFFFFF;
		tbc_new = COL8_000084;
		tc_old  = COL8_C6C6C6;
		tbc_old = COL8_848484;
	} else {
		tc_new  = COL8_C6C6C6;
		tbc_new = COL8_848484;
		tc_old  = COL8_FFFFFF;
		tbc_old = COL8_000084;
	}
	for (y = 3; y <= 20; y++) {
		for (x = 3; x <= xsize - 4; x++) {
			c = buf[y * xsize + x];
			if (c == tc_old && x <= xsize - 22) {
				c = tc_new;
			} else if (c == tbc_old) {
				c = tbc_new;
			}
			buf[y * xsize + x] = c;
		}
	}
	sheet_refresh(sht, 3, 3, xsize, 21);
	return;
}

我们对cmd_app也进行了修改,主要修改了应用程序结束时自动关闭窗口的部分。因为没有运行应用程序的命令行窗口,其task也不为0,所以需要通过flags的0x10比特位来判断是否自动关闭。

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) {
		...
		for (i = 0; i < MAX_SHEETS; i++) {
				sht = &(shtctl->sheets0[i]);
				if ((sht->flags & 0x11) == 0x11 && sht->task == task) {
					/* 找到应用程序残留的窗口 */
					sheet_free(sht);	/* 关闭 */
				}
			}
			memman_free_4k(memman, (int) q, segsiz);
		} else {
			cons_putstr0(cons, ".hrb file format error.\n");
		}

我们还修改了hrb_api,为了在打开窗口的地方启用自动关闭窗口的功能,我们将flags和 0x10 进行OR运算。

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, 100, 50);
		sheet_updown(sht, 3);	/* 图层高度3位于task_a之上 */
		reg[7] = (int) sht;
	} else if (edx == 6) {

下面轮到例行的 make run ,成功了!

5 用鼠标切换输入窗口

我们已经实现了输入窗口的切换,着实进步不小,只是用Tab键来进行切换的操作和 Windows 还不一样。在Windows中,只要用鼠标在窗口上点击一下,那个窗口就会被切换到画面的最上方, 而且键盘输入也会自动切换到该窗口。因此,我们的操作系统也可以通过简单的点击就能完成输入切换。
刚才为了实现用Tab键切换,我们修改了大量的代码,而这次我们只需要添加一点点代码就好了,因为只需要通过鼠标操作实现和键盘操作相同的功能而已。

void HariMain(void)
{
	...
	for (;;) {
		...
			if ((mdec.btn & 0x01) != 0) {
						/* 按下左键 */
						if (mmx < 0) {
							/* 如果处于通常模式 */
							/* 按照从上到下的顺序寻找鼠标所指向的图层 */
							for (j = shtctl->top - 1; j > 0; j--) {
								sht = shtctl->sheets[j];
								x = mx - sht->vx0;
								y = my - sht->vy0;
								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) {
											cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x);
											key_win = sht;
											cursor_c = keywin_on(key_win, sht_win, cursor_c);
					/*到此结束*/					}
										if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) {
											mmx = mx;	/* 进入窗口移动模式 */
											mmy = my;
										}
										if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) {
											/* X */
											...
										break;
									}
								}

“make run” , 点击一下应用程序窗口试试看吧。
在这里插入图片描述

6 定时器

API 到现在为止,计划今天要讲的关于窗口操作功能的部分已经全部完成了,不过现在时间还早,我们来做点好玩的吧。今天我们还没有写过新的 API,那现在我们就来做个新的API吧,比如说,定时器的AP1。
记得在12天中我们实现了用定时器来计时的功能,当时我们高兴了半天,因为我们的操作系统可以用来泡面了。不过现在我们的操作系统 又回到了派不上什么用场的状态,即便可以显示窗口、画点、画直线,但它却不能帮我们解决实际问题呀。
因此,接下来我们打算让应用程序也可以使用定时器,然后就可以去吃碗泡面了。大家先把 想吃的泡面还有开水准备好,趁这段时间我先来写程序了哦! 这次要编写的API如下:
获取定时器(alloc)
EDX=16
EAX=定时器句柄(由操作系统返回)

设置定时器的发送数据(init)
EDX=17
EBX=定时器句柄
EAX=数据

定时器时间设定(set)
EDX=18
EBX=定时器句柄
EAX=时间

释放定时器(free)
EDX=19
EBX=定时器句柄

int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
	...
	} else if (edx == 15) {
		for (;;) {
			...
			if (i >= 256) { /*键盘数据(通过任务A)等*/
				reg[7] = i - 256;
				return 0;
			}
		}
		/*从此开始*/
	} else if (edx == 16) {
		reg[7] = (int) timer_alloc();
	} else if (edx == 17) {
		timer_init((struct TIMER *) ebx, &task->fifo, eax + 256);
	} else if (edx == 18) {
		timer_settime((struct TIMER *) ebx, eax);
	} else if (edx == 19) {
		timer_free((struct TIMER *) ebx);
	}
	/*到此结束*/
	return 0;
}

edx的取值为16~19,这个很简单,应该不用讲了。哦对了,edx=17的情况需要稍微说一下,这里数据的编号加上了256,是因为在向应用程序传递FIFO数据时,需要先减去256。
此外,api_getkey,也就是edx=15的部分也稍微修改了一下。之前我们写的是计(256 <= I && I <= 511),而现在修改成了if(I >= 256,就是说,512以上的值也可以通过api_getkey来获取了。之所以要这样改,是因为现在应用程序不仅需要接收键盘的数据,还需要接收应用程序所设置的 定时器发生超时时所传递的数据。
下面我们来编写应用程序:

_api_alloctimer:	; int api_alloctimer(void);
		MOV		EDX,16
		INT		0x40
		RET

_api_inittimer:		; void api_inittimer(int timer, int data);
		PUSH	EBX
		MOV		EDX,17
		MOV		EBX,[ESP+ 8]		; timer
		MOV		EAX,[ESP+12]		; data
		INT		0x40
		POP		EBX
		RET

_api_settimer:		; void api_settimer(int timer, int time);
		PUSH	EBX
		MOV		EDX,18
		MOV		EBX,[ESP+ 8]		; timer
		MOV		EAX,[ESP+12]		; time
		INT		0x40
		POP		EBX
		RET

_api_freetimer:		; void api_freetimer(int timer);
		PUSH	EBX
		MOV		EDX,19
		MOV		EBX,[ESP+ 8]		; timer
		INT		0x40
		POP		EBX
		RET
// noodle.c
#include <stdio.h>

int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_putstrwin(int win, int x, int y, int col, int len, char *str);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
int api_getkey(int mode);
int api_alloctimer(void);
void api_inittimer(int timer, int data);
void api_settimer(int timer, int time);
void api_end(void);

void HariMain(void)
{
	char *buf, s[12];
	int win, timer, sec = 0, min = 0, hou = 0;
	api_initmalloc();
	buf = api_malloc(150 * 50);
	win = api_openwin(buf, 150, 50, -1, "noodle");
	timer = api_alloctimer();
	api_inittimer(timer, 128);
	for (;;) {
		sprintf(s, "%5d:%02d:%02d", hou, min, sec);
		api_boxfilwin(win, 28, 27, 115, 41, 7 /* 白色 */);
		api_putstrwin(win, 28, 27, 0 /* 黑色 */, 11, s);
		api_settimer(timer, 100);	/* 1秒 */
		if (api_getkey(1) != 128) {
			break;
		}
		sec++;
		if (sec == 60) {
			sec = 0;
			min++;
			if (min == 60) {
				min = 0;
				hou++;
			}
		}
	}
	api_end();
}

关于noodle.c这里稍微讲解一下,之前操作系统显示的是秒,而现在我们需要显示时、分、秒,这样一来时间看起来会更直观,我们就不需要用泡面的3分钟再乘以60换算成秒了。
当定时器超时时,会产生128这样个值,这个值不是由键盘的编码所使用的,因此除了定时器,别的事件不可能产生这个值。如果产生的数据是128以外的值,那一定是用户按了回车键或者其他什么键,这时应用程序结束退出。
开水准备好了吗? make run 然后运行 noodle.hrb 先往泡面里面倒好开水,3分钟到了,可以吃喽!
在这里插入图片描述

7 取消定时器

我们所实现的定时器功能,其实有个问题,接下来我们来解决它。 这个问题是在应用程序结束之后发生的,请大家想象一下noodle.hrb结束之后的情形。应用程序设置了一个1秒的定时器,当定时器到达指定时间时会产生超时,并向任务发送事先设置的数据。
问题是,如果这时应用程序已经结束了,定时器的数据就会被发送到命令行窗口,而命令行窗口肯定是一头雾水。 为了确认这个问题,我们在harib21g中运行noodle.hrb,按回车键或者其他任意键结束程序看看。大约1秒钟之后,命令行窗口中会自动出现一个神秘的字符。用鼠标按“x”关闭窗口之后,也会出现同样的现象。 要解决这个问题,我们需要取消待机中的定时器,这样一来,就可以在应用程序结束的同时取消定时器,问题也就迎刃而解了。 首先我们来编写用于取消指定定时器的函数:

// timer.c
int timer_cancel(struct TIMER *timer)
{
	int e;
	struct TIMER *t;
	e = io_load_eflags();
	io_cli();	/* 在设置过程中禁止改变定时器状态 */
	if (timer->flags == TIMER_FLAGS_USING) {	/* 是否需要取消? */
		if (timer == timerctl.t0) {
			/* 第一个定时器的取消处理 */
			t = timer->next;
			timerctl.t0 = t;
			timerctl.next = t->timeout;
		} else {
			/* 非第一个定时器的取消处理 */
			/* 找到timer前一个定时器 */
			t = timerctl.t0;
			for (;;) {
				if (t->next == timer) {
					break;
				}
				t = t->next;
			}
			t->next = timer->next; /* 将之前“timer的下一个”指向“timer的下一个” */
		}
		timer->flags = TIMER_FLAGS_ALLOC;
		io_store_eflags(e);
		return 1;	/* 取消处理成功 */
	}
	io_store_eflags(e);
	return 0; /* 不需要取消处理 */
}

详细的解说已经写在程序的注释中了,大家自己理解一下哦。 接下来,我们来编写在应用程序结束时取消全部定时器的函数。在此之前,我们需要在定时 器上增加一个标记,用来区分该定时器是否需要在应用程序结束时自动取消。如果没有这个标记 的话,命令行窗口中用来控制光标闪烁的定时器也会被取消掉了。

struct TIMER {
	struct TIMER *next;
	unsigned int timeout;
	char flags, flags2; /* 这里 */
	struct FIFO32 *fifo;
	int data;
};

为了避免忘记置0, 通常情况下, 这里的flags2为0, 我们来修改一下timer.alloc。

struct TIMER *timer_alloc(void)
{
	int i;
	for (i = 0; i < MAX_TIMER; i++) {
		if (timerctl.timers0[i].flags == 0) {
			timerctl.timers0[i].flags = TIMER_FLAGS_ALLOC;
			timerctl.timers0[i].flags2 = 0;	/* 这里 */
			return &timerctl.timers0[i];
		}
	}
	return 0; /* 没有找到 */
}

接下来,我们将应用程序所申请的定时器的fags2设为1。

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) {
		...
					start_app(0x1b, 1003 * 8, esp, 1004 * 8, &(task->tss.esp0));
			shtctl = (struct SHTCTL *) *((int *) 0x0fe4);
			for (i = 0; i < MAX_SHEETS; i++) {
				sht = &(shtctl->sheets0[i]);
				if ((sht->flags & 0x11) == 0x11 && sht->task == task) {
					/* 找到应用程序残留的窗口 */
					sheet_free(sht);	/* 关闭 */
				}
			}
			timer_cancelall(&task->fifo);	/* 这里 */
			memman_free_4k(memman, (int) q, segsiz);
		} else {
		...

准备完成了,下面我们就编写一个函数,来取消应用程序结束时所不需要的定时器。

void timer_cancelall(struct FIFO32 *fifo)
{
	int e, i;
	struct TIMER *t;
	e = io_load_eflags();
	io_cli();	/* 在设置过程中禁止改变定时器状态 */
	for (i = 0; i < MAX_TIMER; i++) {
		t = &timerctl.timers0[i];
		if (t->flags != 0 && t->flags2 != 0 && t->fifo == fifo) {
			timer_cancel(t);
			timer_free(t);
		}
	}
	io_store_eflags(e);
	return;
}

大功告成了! 我们来 make run,运行noodle.brb,然后让程序结束。 哦哦成功了,神秘字符再也不出现太好了!

总结

好了,今天的内容就到这里吧,明天见哦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2307345.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Visual Studio 中 C/C++ 函数不安全警告(C4996)终极解决方案:分场景实战指南

问题描述 在 Visual Studio 中编写 C/C 代码时&#xff0c;使用 scanf、strcpy、fopen 等传统函数会触发以下警告&#xff1a; C4996: xxx: This function or variable may be unsafe. Consider using xxx_s instead. 根本原因&#xff1a; 这些函数缺乏缓冲区溢出检查&#…

提升数据洞察力:五款报表软件助力企业智能决策

概述 随着数据量的激增和企业对决策支持需求的提升&#xff0c;报表软件已经成为现代企业管理中不可或缺的工具。这些软件能够帮助企业高效处理数据、生成报告&#xff0c;并将数据可视化&#xff0c;从而推动更智能的决策过程。 1. 山海鲸报表 概述&#xff1a; 山海鲸报表…

Materials Studio MS2020在linux系统上的安装包下载地址 支持centos Ubuntu rocky等系统

下载地址&#xff1a;MS2020-linux官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘 Materials Studio 2020是一款功能强大的材料科学计算模拟软件&#xff0c;以下是其详细介绍&#xff1a; 核心模块功能 CASTEP模块&#xff1a;采用平面波赝势方法&#xff0c;适用于周…

【语音编解码】常用的基于神经网络的语音编解码方案对比

引言 随着实时通信与多媒体应用的爆炸式增长&#xff0c;传统语音编解码技术正面临带宽效率与音质保真的双重挑战。近年来&#xff0c;基于深度学习的神经编解码器突破性地将端到端架构、动态码率控制与可解释信号处理相结合&#xff0c;在3kbps以下超低码率场景仍能保持自然语…

DeepSeek行业应用实践报告-智灵动力【112页PPT全】

DeepSeek&#xff08;深度搜索&#xff09;近期引发广泛关注并成为众多企业/开发者争相接入的现象&#xff0c;主要源于其在技术突破、市场需求适配性及生态建设等方面的综合优势。以下是关键原因分析&#xff1a; 一、技术核心优势 开源与低成本 DeepSeek基于开源架构&#xf…

a_init: Unable to get log name. Retval:[-4]是什么故障

突然 接到监控告警 aix数据库内存使用超过阈值&#xff0c;请分析 先看内存使用吧 topas中能看到comp内存使用79%&#xff0c;非计算9% 看看哪个进程占用多呢 占用内存最高的20个进程(aix) ps aux |head -1 ; ps aux|sort -rn 4 |head -20看到rbal进程占用11%&#xff0c;比…

利用node.js搭配express框架写后端接口(一)

Node.js 凭借其高效的非阻塞 I/O 操作、事件驱动架构以及轻量级的特点&#xff0c;成为了开发高性能服务器应用的热门选择。Express 框架作为 Node.js 上最流行的 Web 应用框架之一&#xff0c;以其简洁的 API 和丰富的中间件生态系统&#xff0c;极大地简化了 Web 后端开发流程…

CentOS中shell脚本对多台机器执行下载安装

1.建立免密ssh连接 详情见这篇&#xff1a; CentOS建立ssh免密连接&#xff08;含流程剖析&#xff09;-CSDN博客 2.脚本编写 我这里只是简单写了个demo进行演示&#xff0c;如果服务器很多可以先暂存成文件再逐行读取host进行连接并执行命令 用node1去ssh连接node2和node…

深入剖析:自定义实现C语言中的atoi函数

在C语言的标准库中&#xff0c; atoi 函数是一个非常实用的工具&#xff0c;它能够将字符串形式的数字转换为对应的整数。然而&#xff0c;当我们深入探究其实现原理时&#xff0c;会发现其中蕴含着许多有趣的编程技巧和细节。本文将详细讲解如何自定义实现一个类似 atoi 功能的…

Flutter 学习之旅 之 flutter 在 Android 端读取相册图片显示

Flutter 学习之旅 之 flutter 在 Android 端读取相册图片显示 目录 Flutter 学习之旅 之 flutter 在 Android 端读取相册图片显示 一、简单介绍 二、简单介绍 image_picker 三、安装 image_picker 四、简单案例实现 五、关键代码 代码说明&#xff1a; 一、简单介绍 Fl…

数据结构秘籍(一)线性数据结构

1.数组 数组&#xff08;Array&#xff09;是一种很常见的数据结构。它由相同类型的元素&#xff08;element&#xff09;组成&#xff0c;并且是使用一块连续的内存来存储。 我们直接可以利用元素的索引&#xff08;index&#xff09;计算出该元素对应的存储地址。 数组的特…

Linux(centos)系统安装部署MySQL8.0数据库(GLIBC版本)

前言 MySQL 是一款开源的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;主要用于‌结构化数据的存储、管理和检索‌。 一、检查环境 安装前检查服务器glibc版本&#xff0c;下载对应版本包 rpm -qa | grep glibc mysql安装包及依赖包已整理好&#xff0c…

Redis缓存一致性难题:如何让数据库和缓存不“打架”?

标题&#xff1a;Redis缓存一致性难题&#xff1a;如何让数据库和缓存不“打架”&#xff1f;&#xff08;附程序员脱发指南&#xff09; 导言&#xff1a;当数据库和缓存成了“异地恋” 想象一下&#xff1a;你刚在美团下单了一份麻辣小龙虾&#xff0c;付款后刷新页面&#…

【R包】pathlinkR转录组数据分析和可视化利器

介绍 通常情况下&#xff0c;基因表达研究如微阵列和RNA-Seq会产生数百到数千个差异表达基因&#xff08;deg&#xff09;。理解如此庞大的数据集的生物学意义变得非常困难&#xff0c;尤其是在分析多个条件和比较的情况下。该软件包利用途径富集和蛋白-蛋白相互作用网络&…

1.68M 免安装多格式图片批量转 webp 无广告软件推荐

软件介绍 今天要给大家分享一款超实用的图片处理工具&#xff0c;它能实现多格式图片向 webp 格式的转换&#xff0c;无论是 jpg、png、tif、gif 还是 webp 格式自身的图片&#xff0c;都能批量且借助多线程技术进行转换。 直接打开就能用&#xff0c;体积小巧&#xff0c;仅 …

《Qt窗口动画实战:Qt实现呼吸灯效果》

Qt窗口动画实战&#xff1a;Qt实现呼吸灯效果 在嵌入式设备或桌面应用中&#xff0c;呼吸灯效果是一种常见且优雅的UI动画&#xff0c;常用于指示系统状态或吸引用户注意。本文将介绍如何使用Qt动画框架实现平滑的呼吸灯效果。 一、实现原理 利用Qt自带的动画框架来实现&…

详解Tomcat下载安装以及IDEA配置Tomcat(2023最新)

目录 步骤一&#xff1a;首先确认自己是否已经安装JDK步骤二&#xff1a;下载安装Tomcat步骤三&#xff1a;Tomcat配置环境变量步骤四&#xff1a;验证Tomcat配置是否成功步骤五&#xff1a;为IDEA配置Tomcat 步骤一&#xff1a;首先确认自己是否已经安装JDK jdk各版本通用安…

AI如何通过大数据分析提升制造效率和决策智能化

人工智能&#xff08;AI&#xff09;与大数据技术的融合&#xff0c;不仅重新定义了生产流程&#xff0c;更让企业实现了从“经验驱动”到“数据智能驱动”的跨越式升级。 从“模糊经验”到“精准洞察”​​ 传统制造业依赖人工经验制定生产计划&#xff0c;但面对复杂多变的市…

kafka-关于ISR-概述

一. 什么是ISR &#xff1f; Kafka 中通常每个分区都有多个副本&#xff0c;其中一个副本被选举为 Leader&#xff0c;其他副本为 Follower。ISR 是指与 Leader 副本保持同步的 Follower 副本集合。ISR 机制的核心是确保数据在多个副本之间的一致性和可靠性&#xff0c;同时在 …

使用 Polars 进行人工智能医疗数据分析(ICU数据基本测试篇)

引言 在医疗领域&#xff0c;数据就是生命的密码&#xff0c;每一个数据点都可能蕴含着拯救生命的关键信息。特别是在 ICU 这样的重症监护场景中&#xff0c;医生需要实时、准确地了解患者的病情变化&#xff0c;以便做出及时有效的治疗决策。而随着医疗技术的飞速发展&#x…