30天开发操作系统 第 17 天 -- 命令行窗口

news2025/1/23 6:44:10

前言

今天一开始,请大家先回忆一下任务A的情形。在harib13e中,任务A下面的LEVEL中有任务因此FIFO为空时我们可以让任务A进入休眠状态。那么,如果我们并未启动任务B0~ B0~ B2, B2的话,任务A又将会如何呢? 首先,如果我们不对任务A进行任何改写,就按照它现在的样子进入休眠状态的话,大家想一想,会发生什么呢?一旦任务A休眠,mtask.c将自动寻找下层LEVEL中的任务,但由于这一次 我们没有启动任务B0~B2,因此程序就找不到其他的任务而导致运行出现异常。 如果这样不行,那我们把休眠的部分再改回io_hlt;不就好了吗?这样一来,不但程序运行不会出现异常,还能省电呢…话虽如此,但如果一个操作系统要根据下层LEVEL是否 存在任务来改写程序的话,大家觉得靠谱吗?即使不改写程序,也能自动在适当的 LEVEL运行适当的任务,这样的操作系统才是优秀的操作系统。 因此,一般情况下可以让任务休眠,但当所有LEVEL中都没有任务存在的时候,就需要 HTL 了。接下来我们就按照这个要求来改写mtask.c。

一、书接上文,闲置任务

我们确实可以这样来改, 那么我们从task_sleep开始改起吧。 但是改写之后task_sleep 将变得更加复杂,速度也会打折扣(说是会打折扣,其实应该也就是稍微慢一点点而已啦)其实我们还有更好的方法。 如果“所有LEVEL中都没有任务”就会出问题,那我们只要避免这种情况发生不就可以了吗? 这类似于我们写定时器的时候所采用的“卫兵”的思路。

void task_idle(void)
{
	for (;;) {
		io_hlt();
	}
}

我们创建这样一个任务,并把它一直放在最下层LEVEL中,大家觉得如何? “idle”(闲置) 就是代表“不工作,空闲”的意思,这个任务的功能只是执行HTL。
这样一来,即便任务A进入休眠状态, 系统也会自动切换到上面这个闲置任务,于是便开始执行HTL。当我们移动鼠标,FIFO中有数据写入的时候,任务A就会被唤醒,系统会自动切换到任务A继续工作。
我们完全不需要对task_sleep等代码进行任何改动,只需在task_init中将这个闲置 任务放在最下层LEVEL中就可以了。

struct TASK *task_init(struct MEMMAN *memman)
{
	int i;
	struct TASK *task, *idle;
	...

	idle = task_alloc();
	idle->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024;
	idle->tss.eip = (int) &task_idle;
	idle->tss.es = 1 * 8;
	idle->tss.cs = 2 * 8;
	idle->tss.ss = 1 * 8;
	idle->tss.ds = 1 * 8;
	idle->tss.fs = 1 * 8;
	idle->tss.gs = 1 * 8;
	task_run(idle, MAX_TASKLEVELS - 1, 1);

	return task;
}

接着我们来测试一下, 将HariMain改成下面这样。

void HariMain(void)
{
	...
	/* sht_win_b */
	for (i = 0; i < 3; i++) {
		sht_win_b[i] = sheet_alloc(shtctl);
		buf_win_b = (unsigned char *) memman_alloc_4k(memman, 144 * 52);
		sheet_setbuf(sht_win_b[i], buf_win_b, 144, 52, -1); 
		sprintf(s, "task_b%d", i);
		make_window8(buf_win_b, 144, 52, s, 0);
		task_b[i] = task_alloc();
		task_b[i]->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
		task_b[i]->tss.eip = (int) &task_b_main;
		task_b[i]->tss.es = 1 * 8;
		task_b[i]->tss.cs = 2 * 8;
		task_b[i]->tss.ss = 1 * 8;
		task_b[i]->tss.ds = 1 * 8;
		task_b[i]->tss.fs = 1 * 8;
		task_b[i]->tss.gs = 1 * 8;
		*((int *) (task_b[i]->tss.esp + 4)) = (int) sht_win_b[i];
		/* task_run(task_b[i], 2, i + 1); 这里*/
	}
	...
}

这样就只剩下任务A和task_idle 了。也就是说, 我们不让系统运行任务B0~B2, 当然, 这样改只是不启动任务而已,窗口还是会照常显示的。 然后我们来 make run”,画面如下。
在这里插入图片描述

二、创建命令行窗口

1.雏形

所谓命令行窗口,就是大家在运行 “make run” 的时候所使用的那个黑底白字的,在里面输入文件名就可以运行程序的东西。接下来我们就做一个试试看。 “这玩意儿能算是命令行窗口吗?”我们一开始做出来的东西很可能带来这样的疑问,不过 没关系,我们会让它逐步发展壮大,最终实现可以启动应用程序的功能。你看,是不是越来越像个操作系统的样子了呢?大家一定迫不及待了吧。
我们并不打算将命令行窗口作为任务A的一部分,而是单独做成一个新的任务。这样一来,就像任务B0-B2-样,我们可以很容易地创建多个命令行窗口。
我们对任务B的程序进行一些修改,并将任务A程序的一部分融合进去,就写成了下面这个样子。计数我们已经玩够了,所以把计数的代码从任务B中删除。

void HariMain(void)
{
	/* sht_cons */
	sht_cons = sheet_alloc(shtctl);
	buf_cons = (unsigned char *) memman_alloc_4k(memman, 256 * 165);
	sheet_setbuf(sht_cons, buf_cons, 256, 165, -1); 
	make_window8(buf_cons, 256, 165, "console", 0);
	make_textbox8(sht_cons, 8, 28, 240, 128, COL8_000000);
	task_cons = task_alloc();
	task_cons->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
	task_cons->tss.eip = (int) &console_task;
	task_cons->tss.es = 1 * 8;
	task_cons->tss.cs = 2 * 8;
	task_cons->tss.ss = 1 * 8;
	task_cons->tss.ds = 1 * 8;
	task_cons->tss.fs = 1 * 8;
	task_cons->tss.gs = 1 * 8;
	*((int *) (task_cons->tss.esp + 4)) = (int) sht_cons;
	task_run(task_cons, 2, 2); /* level=2, priority=2 */
	...
	sheet_slide(sht_back,  0,  0);
	sheet_slide(sht_cons, 32,  4);
	sheet_slide(sht_win,  64, 56);
	sheet_slide(sht_mouse, mx, my);
	sheet_updown(sht_back,  0);
	sheet_updown(sht_cons,  1);
	sheet_updown(sht_win,   2);
	sheet_updown(sht_mouse, 3);
	sprintf(s, "(%3d, %3d)", mx, my);
	...
}


void console_task(struct SHEET *sheet)
{
	struct FIFO32 fifo;
	struct TIMER *timer;
	struct TASK *task = task_now();

	int i, fifobuf[128], cursor_x = 8, cursor_c = COL8_000000;
	fifo32_init(&fifo, 128, fifobuf, task);

	timer = timer_alloc();
	timer_init(timer, &fifo, 1);
	timer_settime(timer, 50);

	for (;;) {
		io_cli();
		if (fifo32_status(&fifo) == 0) {
			task_sleep(task);
			io_sti();
		} else {
			i = fifo32_get(&fifo);
			io_sti();
			if (i <= 1) { /* 光标用计时器 */
				if (i != 0) {
					timer_init(timer, &fifo, 0); /* 下次置位 0 */
					cursor_c = COL8_FFFFFF;
				} else {
					timer_init(timer, &fifo, 1); /* 下次置位 1 */
					cursor_c = COL8_000000;
				}
				timer_settime(timer, 50);
				boxfill8(sheet->buf, sheet->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43);
				sheet_refresh(sheet, cursor_x, 28, cursor_x + 8, 44);
			}
		}
	}
}

有一个地方需要解释一下,就是console_task中task=tasknow();这里。在console_task中需要执行休眠,因此必须要知道自己本身的TASK 结构所在的内存地址。由于HariMain中准备了可以像sheet那样从HariMain传入这个地址,但那样也有点过于繁琐了。忽然想到在 task_cons, mtask.c中不是有个task_now函数吗,就用它来获取TASK地址好了。

作为我们的命令行窗口雏形, 看上去还不赖。
在这里插入图片描述

2.切换输入窗口

命令行窗口这东西,如果不能在里面输人字符的话就毫无用处了,因此我们得让它能接受字符输入才行。不过现在无论我们输入什么字符,都会跑到任务A的窗口中去,所以为了能够往命令行窗口中输入字符,我们要让系统在按下“Tab”键的时候,将输入窗口切换到命令行窗口上去。
虽说是切换窗口,其实我们只是先将窗口标题栏的颜色改一改而已啦(苦笑)。真正负责输人切换的部分我们下一小节再写,改变先从表面工夫开始吧。

要改变窗口标题栏颜色,最好将makewindow8中描绘窗口标题栏的代码,和描绘窗口剩余部分的代码区分开来,我们将这个函数改写一下。

void make_window8(unsigned char *buf, int xsize, int ysize, char *title, char act)
{
	boxfill8(buf, xsize, COL8_C6C6C6, 0,         0,         xsize - 1, 0        );
	boxfill8(buf, xsize, COL8_FFFFFF, 1,         1,         xsize - 2, 1        );
	boxfill8(buf, xsize, COL8_C6C6C6, 0,         0,         0,         ysize - 1);
	boxfill8(buf, xsize, COL8_FFFFFF, 1,         1,         1,         ysize - 2);
	boxfill8(buf, xsize, COL8_848484, xsize - 2, 1,         xsize - 2, ysize - 2);
	boxfill8(buf, xsize, COL8_000000, xsize - 1, 0,         xsize - 1, ysize - 1);
	boxfill8(buf, xsize, COL8_C6C6C6, 2,         2,         xsize - 3, ysize - 3);
	boxfill8(buf, xsize, COL8_848484, 1,         ysize - 2, xsize - 2, ysize - 2);
	boxfill8(buf, xsize, COL8_000000, 0,         ysize - 1, xsize - 1, ysize - 1);
	make_wtitle8(buf, xsize, title, act);
	return;
}


void make_wtitle8(unsigned char *buf, int xsize, char *title, char act)
{
	static char closebtn[14][16] = {
		"OOOOOOOOOOOOOOO@",
		"OQQQQQQQQQQQQQ$@",
		"OQQQQQQQQQQQQQ$@",
		"OQQQ@@QQQQ@@QQ$@",
		"OQQQQ@@QQ@@QQQ$@",
		"OQQQQQ@@@@QQQQ$@",
		"OQQQQQQ@@QQQQQ$@",
		"OQQQQQ@@@@QQQQ$@",
		"OQQQQ@@QQ@@QQQ$@",
		"OQQQ@@QQQQ@@QQ$@",
		"OQQQQQQQQQQQQQ$@",
		"OQQQQQQQQQQQQQ$@",
		"O$$$$$$$$$$$$$$@",
		"@@@@@@@@@@@@@@@@"
	};
	int x, y;
	char c, tc, tbc;
	if (act != 0) {
		tc = COL8_FFFFFF;
		tbc = COL8_000084;
	} else {
		tc = COL8_C6C6C6;
		tbc = COL8_848484;
	}
	boxfill8(buf, xsize, tbc, 3, 3, xsize - 4, 20);
	putfonts8_asc(buf, xsize, 24, 4, tc, title);
	for (y = 0; y < 14; y++) {
		for (x = 0; x < 16; x++) {
			c = closebtn[y][x];
			if (c == '@') {
				c = COL8_000000;
			} else if (c == '$') {
				c = COL8_848484;
			} else if (c == 'Q') {
				c = COL8_C6C6C6;
			} else {
				c = COL8_FFFFFF;
			}
			buf[(5 + y) * xsize + (xsize - 21 + x)] = c;
		}
	}
	return;
}

这样就差不多了,接下来我们来改写HariMMain。

void HariMain(void)
{
	...
	int key_to = 0;
	
		for (;;) {
		io_cli();
		for (;;) {
		io_cli();
		if (fifo32_status(&fifo) == 0) {
			task_sleep(task_a);
			io_sti();
		} else {
			i = fifo32_get(&fifo);
			io_sti();
			if (256 <= i && i <= 511) { /* 键盘数据 */
				sprintf(s, "%02X", i - 256);
				putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, s, 2);
				if (i < 0x54 + 256) {
					...
				}
				if (i == 256 + 0x0e && cursor_x > 8) { /* 退格键 */				...
				}
				if (i == 256 + 0x0f) { /* Tab */
				...
				} else {
						key_to = 0;
						make_wtitle8(buf_win,  sht_win->bxsize,  "task_a",  1);
						make_wtitle8(buf_cons, sht_cons->bxsize, "console", 0);
					}
					sheet_refresh(sht_win,  0, 0, sht_win->bxsize,  21);
					sheet_refresh(sht_cons, 0, 0, sht_cons->bxsize, 21);
				}
				boxfill8(sht_win->buf, sht_win->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43);
				sheet_refresh(sht_win, cursor_x, 28, cursor_x + 8, 44);
			} else if (512 <= i && i <= 767) { /* 鼠标数据 */
			...
			} else if (i <= 1) {
			}
		}
	}
}

这段代码的重点在于key to这个变量,用于记录键盘输入(key)应该发送到(to)哪里。为0则发送到任务A,为1则发送到命令行窗口任务。
还是一如既往地“make run”,然后按下Tab键试试看。
哇,颜色变了耶!真不错啊。心情好激动,先按个10次Tab键看看。嗒…
然后又很得意地输入了“abc”试试看,果然,结果是这个样子。
在这里插入图片描述
好,接下来我们就来实现键盘输人啦!

3.字符输入

要实现字符的输入,只要在键盘被按下的时候向console task的FIFO发送数据即可。但要发 送数据,必须要知道structFIFO的内存地址才行。唔,这可怎么办呢? 我们可以让任务A在创建taskcons的时候,顺便将FIFO也准备好。这样一来,任务A就能知 道task_cons所使用的FIFO的地址, 我们的问题便迎刃而解了。 等等,这样还是太麻烦了,我们还是把struct FIFO放到struct TASK里面去吧。基本上没有什么任务是完全用不到FIFO的, 因此我们把它们绑定起来。

struct TASK {
	int sel, flags; 
	int level, priority;
	struct FIFO32 fifo;/* 这里 */
	struct TSS32 tss;
};

接下来我们来修改 一 HariMain, 使其判断key_to的值并向task_cons的FIFO发送数据。

void HariMain(void)
{
	...
	for (;;) {
		io_cli();
		if (fifo32_status(&fifo) == 0) {
			task_sleep(task_a);
			io_sti();
		} else {
			i = fifo32_get(&fifo);
			io_sti();
			if (256 <= i && i <= 511) { /* 键盘数据 */
				sprintf(s, "%02X", i - 256);
				putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, s, 2);
				if (i < 0x54 + 256 && keytable[i - 256] != 0) { /* 一般字符 */
					if (key_to == 0) {	/* 发送给任务A */
						if (cursor_x < 128) {
							/* 显示一个字符之后将光标后移一位 */
							s[0] = keytable[i - 256];
							s[1] = 0;
							putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1);
							cursor_x += 8;
						}
					} else {	/* 发送给命令行窗口 */
						fifo32_put(&task_cons->fifo, keytable[i - 256] + 256);
					}
				}
				if (i == 256 + 0x0e) {	/* 退格键 */
					if (key_to == 0) {	/* 发送给任务A */
						if (cursor_x > 8) {
							/* 用空白擦除光标后将光标前移一位 */
							putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1);
							cursor_x -= 8;
						}
					} else {	/* 发送给命令行窗口 */
						fifo32_put(&task_cons->fifo, 8 + 256);
					}
				}
				if (i == 256 + 0x0f) { /* Tab */
					if (key_to == 0) {
						key_to = 1;
						make_wtitle8(buf_win,  sht_win->bxsize,  "task_a",  0);
						make_wtitle8(buf_cons, sht_cons->bxsize, "console", 1);
					} else {
						key_to = 0;
						make_wtitle8(buf_win,  sht_win->bxsize,  "task_a",  1);
						make_wtitle8(buf_cons, sht_cons->bxsize, "console", 0);
					}
					sheet_refresh(sht_win,  0, 0, sht_win->bxsize,  21);
					sheet_refresh(sht_cons, 0, 0, sht_cons->bxsize, 21);
				}
				/* 重新显示光标 */
				boxfill8(sht_win->buf, sht_win->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43);
				sheet_refresh(sht_win, cursor_x, 28, cursor_x + 8, 44);
			} else if (512 <= i && i <= 767) { 
				...
			} else if (i <= 1) { /*  */
				...
			}
		}
	}
}

系统会向命令行窗口任务发送键盘数据, 支持一般的字符输入和退格键。当key_to不为0时, 由于在命令行窗口中也使用了定时器等,为了不与键盘数据冲突,我们在写入FIFO的时候将键盘数据的值加上256。

在向命令行窗口发送键盘数据的时候,并不是直接发送从键盘接收到的原始数据,而是发送 经过keytable[]转换后的值。究其原因,是由于这 样做可以省去在命令行窗口任务中将按键编码转 换成字符编码的步骤。 对于退格键,我们将它的字符编码定义为8,因为在ASCII码中, 编码8就对应着退格键,我们只是和它接轨而已。当然,如果你不想用8也完全没有问题。

console_task也需要改写一下,因为我们必须让它能够接收并处理键盘数据。此外, 我们还得把&fifo改写成&task-fifo:

void console_task(struct SHEET *sheet)
{
	struct TIMER *timer;
	struct TASK *task = task_now();
	int i, fifobuf[128], cursor_x = 16, cursor_c = COL8_000000;
	char s[2];

	fifo32_init(&task->fifo, 128, fifobuf, task);
	timer = timer_alloc();
	timer_init(timer, &task->fifo, 1);
	timer_settime(timer, 50);

	/* 显示提示符 */
	putfonts8_asc_sht(sheet, 8, 28, COL8_FFFFFF, COL8_000000, ">", 1);

	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 != 0) {
					timer_init(timer, &task->fifo, 0); /* 下次 0 */
					cursor_c = COL8_FFFFFF;
				} else {
					timer_init(timer, &task->fifo, 1); /* 下次 1 */
					cursor_c = COL8_000000;
				}
				timer_settime(timer, 50);
			}
			if (256 <= i && i <= 511) { /* 键盘数据 */
				if (i == 8 + 256) {
					/* 退格键 */
					if (cursor_x > 16) {
						/* 用空白擦除光标后将光标前移一位 */
						putfonts8_asc_sht(sheet, cursor_x, 28, COL8_FFFFFF, COL8_000000, " ", 1);
						cursor_x -= 8;
					}
				} else {
					/* 一般字符 */
					if (cursor_x < 240) {
						/* 显示一个字符之后将光标后移一位 */
						s[0] = i - 256;
						s[1] = 0;
						putfonts8_asc_sht(sheet, cursor_x, 28, COL8_FFFFFF, COL8_000000, s, 1);
						cursor_x += 8;
					}
				}
			}
			/* 重新显示光标 */
			boxfill8(sheet->buf, sheet->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43);
			sheet_refresh(sheet, cursor_x, 28, cursor_x + 8, 44);
		}
	}
}

上面的程序基本是照猫画虎而来, 唯一的一点区别就是开头显示提示符“>”的 地方了。退格键的处理上也对可以删除的界限作了调整,以避免退格时擦掉提示符。
我们来运行一下看看, make run
在这里插入图片描述

成功了,真是个伟大的胜利。现在我们可以输入英文、数字和符号了,但还无法输入“!” 和“%”。

4.符号的输入

我们这就要实现“!”和“%” 的输人。为了能够输人“!”和“%”,我们必须要处理Shif键。根据按键编码表,Shif键的按键编码如下(觉得看表格看麻烦的话,可以自己 “make run" 一下, 按键的时候屏幕上会显示出按键编码哦)。
在这里插入图片描述

因此, 我们准备一个key_shift变量, 当左Shif按下时置为1, 右Shif按下时置为2,两个都不按时置为0,两个都按下(有人会这么干吗? )的时候就置为3。当key_shift为0时,我们用keytable0[ ]将按键编码转换为字符编码,而当key_shift不为0时,则 使用keytable1[ ]进行转换。(大家还有什么好的想法吗?)

就是下面这样将上面的思路用程序写到HariMain中:

void HariMain(void)
{
	...
	static char keytable0[0x80] = {
		0,   0,   '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '^', 0,   0,
		'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '@', '[', 0,   0,   'A', 'S',
		'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', ':', 0,   0,   ']', 'Z', 'X', 'C', 'V',
		'B', 'N', 'M', ',', '.', '/', 0,   '*', 0,   ' ', 0,   0,   0,   0,   0,   0,
		0,   0,   0,   0,   0,   0,   0,   '7', '8', '9', '-', '4', '5', '6', '+', '1',
		'2', '3', '0', '.', 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
		0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
		0,   0,   0,   0x5c, 0,  0,   0,   0,   0,   0,   0,   0,   0,   0x5c, 0,  0
	};
	static char keytable1[0x80] = {
		0,   0,   '!', 0x22, '#', '$', '%', '&', 0x27, '(', ')', '~', '=', '~', 0,   0,
		'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '`', '{', 0,   0,   'A', 'S',
		'D', 'F', 'G', 'H', 'J', 'K', 'L', '+', '*', 0,   0,   '}', 'Z', 'X', 'C', 'V',
		'B', 'N', 'M', '<', '>', '?', 0,   '*', 0,   ' ', 0,   0,   0,   0,   0,   0,
		0,   0,   0,   0,   0,   0,   0,   '7', '8', '9', '-', '4', '5', '6', '+', '1',
		'2', '3', '0', '.', 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
		0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
		0,   0,   0,   '_', 0,   0,   0,   0,   0,   0,   0,   0,   0,   '|', 0,   0
	};
	int key_to = 0, key_shift = 0;
	...
	for (;;) {
		io_cli();
		if (fifo32_status(&fifo) == 0) {
			task_sleep(task_a);
			io_sti();
		} else {
			i = fifo32_get(&fifo);
			io_sti();
			if (256 <= i && i <= 511) { /* 键盘数据 */
				sprintf(s, "%02X", i - 256);
				putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, s, 2);
				if (i < 0x80 + 256) { /* 将按键编码转换为字符编码 */
					if (key_shift == 0) {
						s[0] = keytable0[i - 256];
					} else {
						s[0] = keytable1[i - 256];
					}
				} else {
					s[0] = 0;
				}
				if (s[0] != 0) { /* 一般字符 */
					if (key_to == 0) {	/* 发送给任务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(&task_cons->fifo, s[0] + 256);
					}
				}
				if (i == 256 + 0x0e) {	/* 退格键 */
					...
				}
				if (i == 256 + 0x0f) {	/* Tab */
					...
				}
				if (i == 256 + 0x2a) {	/* 左 Shift ON */
					key_shift |= 1;
				}
				if (i == 256 + 0x36) {	/* 右 Shift ON */
					key_shift |= 2;
				}
				if (i == 256 + 0xaa) {	/* 左 Shift OFF */
					key_shift &= ~1;
				}
				if (i == 256 + 0xb6) {	/* 右 Shift OFF */
					key_shift &= ~2;
				}
			...
			} else if (512 <= i && i <= 767) {
				...
			} else if (i <= 1) { 
				...
			}
}

关于keytable0[ ]和keytable1[ ],考虑到顺便支持“\”和“_”的输入也不错,就让它一直支持到0x80的转换吧。在keytable1[ ]中,对于英文字母和小键盘的部分没有进行改动。
程序的原理是,先将按键编码转换成字符编码, 则向s[0]存入0。如果遇到无法转换的按键编码,则向s[0]存入0。剩下的部分没有什么难度,只要仔细读读程序应该就可以理解了吧。
在这里插入图片描述

虽然我们只修改了任务A,但在命令行窗口中也可以输入符号了,真不错。不过现在我们输入的英文字母都显示成大写,这个看着实在不舒服,因此我们需要实现小写字母的输入,一起看下一小节吧。

5.大小写字母

要实现区分大写、小写字母的输入,我们必须要 #同时判断Shif键的状态以及CapsLock的状态。
CapsLock 为 OFF & Shift健为0FF → 小写英文字母
CapsLock为OFF & Shift健为ON → 大写英文字
CapsLock 为ON & Shift健为OFF → 大写英文字
CapsLock为ON & Shift健为ON → 小写英文字母

我们可以将需要转换为小写字母的条件总结如下:
输人的字符为英文字母
“CapsLock为OFF&Shif键为OFF” 或者 “CapsLock为ON& Shif键为ON”

我们已经知道如何获取Shif键的状态, 但是CapsLock的状态要如何获取呢?BIOS知道 CapsLock 的状态, 可现在我们处于32位模式, 没办法向BIOS查询。不过别担心, asmhead.nas 中我们已经从BIOS获取到了键盘状态, 就保存在binfo→>leds中。
binfo->leds的第4位→ScrollLock状态
binfo->leds的第5位→ NumLock状态
binfo->leds的第6位→CapsLock状态
我们就可以处理大小写字母的输入了。 只要使用上述数据,

在i语句中,除了 && 运算符,还有一个 || 运算符,这个运算符代表“只要其中任意一个条件成立即可”的意思,我们可以用它来改写HariMain, 使其能够实现小写字母的输入。

void HariMain(void)
{
	...
	int key_to = 0, key_shift = 0, key_leds = (binfo->leds >> 4) & 7;
	...
	for (;;) {
		io_cli();
		if (fifo32_status(&fifo) == 0) {
			task_sleep(task_a);
			io_sti();
		} else {
			i = fifo32_get(&fifo);
			io_sti();
			if (256 <= i && i <= 511) { 
				sprintf(s, "%02X", i - 256);
				putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, s, 2);
				if (i < 0x80 + 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) {
					...
				} else if (512 <= i && i <= 767) {
					...
				} else if (i <= 1) {
					...
				}
				...
		}
}

在这里插入图片描述

呀,这样就变得酷多了吧。啥?不能满足于这点成绩?其实这样挺好的不是吗,一步一个脚印,享受每一次进步,这样才有成就感呀。我们已经实现了根据CapsLock的状态来切换大小写字母的输入, 那大家想不想实现在按下CapsLock键的时候切换CapsLock的状态呢?一定很想吧?

6.对各种锁定键的支持

好,让我们开始吧。回头再看一遍第14天的编码表(不想看表格的同学还是可以自己按键盘看编码哦),我们可以得到:
0x3a: CapsLock 0x45: NumLock 0x46: Scrolllock
只要将binfo->leds中对应的位置改写就可以了。因此当我们接收到上述按键编码时, 这和key_shift基本上是一样的,很容易实现。 到这里,我们已经实现了锁定键模式的切换,不过现在还是有一个问题,模式是可以切换了, 但是键盘上面的指示灯却不会发生变化。这样就可能会发生下述情况:明明CapsLock灯没亮,但在系统中却是处于CapsLock模式。这个问题我们最好想办法解决它。

关于LED的控制可采用下面的方法向键盘发送指令和数据。
■对于NumLock和CapsLock等LED的控制, 读取状态寄存器, 等待bit1的值变为0。
■向数据输出(0060)写入要发送的1个字节数据。
■等待键盘返回1个字节的信息,这和等待键盘输入所采用的方法相同(用IRQ等待或者用轮询状态寄存器bit1的值直到其变为0都可以)。
■返回的信息如果为0xfa,表明1个字节的数据已成功发送给键盘。如为0xfe则表明 发送失败,需要返回第1步重新发送。
■要控制LED的状态,需要按上述方法执行两次,向键盘发送EDxx数据。其中,xx的bit 0代表ScrollLock, bit 1代表NumLock,bit 2代表CapsLock (0表示熄灭,1表示点亮)。 bit 3~7为保留位, 置0即可。

有了这些信息,我们总算看到希望了, 于是我们写了以下程序。

#define KEYCMD_LED		0xed

void HariMain(void)
{
	...
	struct FIFO32 fifo, keycmd;
	int fifobuf[128], keycmd_buf[32];
	...
	int key_to = 0, key_shift = 0, key_leds = (binfo->leds >> 4) & 7, keycmd_wait = -1;
	...
	/* 为了避免和键盘当前状态冲突,在一开始先进行设置 */
	fifo32_put(&keycmd, KEYCMD_LED);
	fifo32_put(&keycmd, key_leds);
	fifo32_init(&keycmd, 32, keycmd_buf, 0);
	for (;;) {
		/*从此开始*/
		if (fifo32_status(&keycmd) > 0 && keycmd_wait < 0) {
			/* 如果存在向健盘控制器发送的数据,则发送它 */
			keycmd_wait = fifo32_get(&keycmd);
			wait_KBC_sendready();
			io_out8(PORT_KEYDAT, keycmd_wait);
		}
		/*到此结束*/
		io_cli();
		if (fifo32_status(&fifo) == 0) {
			task_sleep(task_a);
			io_sti();
		} else {
			i = fifo32_get(&fifo);
			io_sti();
			if (256 <= i && i <= 511) {
				...
				/*从此开始*/
				if (i == 256 + 0x3a) {	/* CapsLock */
					key_leds ^= 4;
					fifo32_put(&keycmd, KEYCMD_LED);
					fifo32_put(&keycmd, key_leds);
				}
				if (i == 256 + 0x45) {	/* NumLock */
					key_leds ^= 2;
					fifo32_put(&keycmd, KEYCMD_LED);
					fifo32_put(&keycmd, key_leds);
				}
				if (i == 256 + 0x46) {	/* ScrollLock */
					key_leds ^= 1;
					fifo32_put(&keycmd, KEYCMD_LED);
					fifo32_put(&keycmd, key_leds);
				}
				if (i == 256 + 0xfa) {	/* 键盘成功接收到数据 */
					keycmd_wait = -1;
				}
				if (i == 256 + 0xfe) {	/* 键盘没有成功接收到数据 */
					wait_KBC_sendready();
					io_out8(PORT_KEYDAT, keycmd_wait);
				}
				...
				} else if (512 <= i && i <= 767) {
					...
				} else if (i <= 1) { 
					...
				}
			}
}		

程序的工作原理是这样的。首先,我们创建了一个叫keycmd的FIFO缓冲区,它不是用来接收中断请求的,而是用来管理由任务A向键盘控制器发送数据的顺序的。如果有数据要发送到键盘控制器,首先会在这个keycmd中累积起来。 keycnd_wait变量,用来表示向键盘控制器发送数据的状态。当keycmd_wait的值为-1时,表示键盘控制器处于通常状态,可以发送指令;当值不为-1时,表示键盘控制器正在等待发送的数据,这时要发送的数据被保存在keycmd_wait变量中。
在for循环的开头, 当keycmd中有数据, 且keycmd_wait为-1时,向键盘发送1个字节的数据, 在开始发送数据的同时,keycmd_wait变为非-1的值。随后, 当从键盘接收到0xfa返回信息时, 继续发送下一个数据。 当从键盘接收到的返回信息为0xfe时,则重新发 keycmd_wait恢复为-1, 送刚才的数据。 在for循环前面,我们向键盘控制器设置了指示灯的状态,也许这一段是可有可无的,不过这样可以保证key_leds的值和实际的键盘指示灯状态绝对不会发生冲突的情况, 因此保险起见还是设置了。

好了,我们来“make run”。本来想贴一张运行时的截图,不过这里发生变化的不是屏幕画 面,而是键盘的指示灯,所以很遗憾, 没有办法给大家展示这个令人感动的场面了。 嗯?不管怎么按CapsLock键, 都无法点亮指示灯,不过NumLock和 按Shift +CapsLock指示灯就亮了,好奇怪啊,我们明明不是这样设计的。
由于实在无法理解这一现象, 又重新 make run了harib14f,按下NumLock进行实验, harib14f中也可以点亮NumLock指示灯”。 看起来在这个QEMU模拟器中,键盘的指示灯貌似并不是由模拟器管理, 而是由Windows管理的。

总结

好,今天就到这里吧, 明天我们继续来做命令行窗口哦!

今天是小年,预热春节,祝大家小年安康!

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

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

相关文章

阿九的python 爬虫进阶课18.3 学习笔记

文章目录 前言1. 爬取大标题2. 爬取小标题3. 证券栏下的标题4. 某篇文章里的具体内容 前言 网课链接&#xff1a;https://www.bilibili.com/video/BV1kV4y1576b/新浪财经网址&#xff1a;https://finance.sina.com.cn/需先下载库&#xff1a; conda install lxml布置爬取的一…

Qt 5.14.2 学习记录 —— 십팔 对话框

文章目录 1、Qt对话框2、自定义对话框1、代码方式2、图形化方式 3、模态对话框4、QMessageBox5、QColorDialog6、QFileDialog7、QFontDialog8、QInputDialog 1、Qt对话框 Qt的对话框用QDialog类来表示&#xff0c;可以自定义一些类来实现自定义对话框&#xff0c;但需要继承自…

web3py+flask+ganache的智能合约教育平台

最近在学习web3的接口文档&#xff0c;使用web3pyflaskganache写了一个简易的智能合约教育平台&#xff0c;语言用的是python&#xff0c;ganche直接使用的本地区块链网络&#xff0c;用web3py进行交互。 代码逻辑不难&#xff0c;可以私信或者到我的闲鱼号夏沫mds获取我的代码…

java中的String类、StringBuffer类、StringBuilder类的详细讲解(包含相互之间的比较)

文章目录 一、String 类1 String 类的介绍2 String 对象创建的两种方式3 测试题加深理解&#xff08;1&#xff09; 例题一&#xff08;2&#xff09;例题二&#xff08;3&#xff09; 例题三 4 String 类的常用方法&#xff08;1&#xff09;equals()&#xff08;2&#xff09…

外设链接与中断

外设链接与中断 PC键盘处理过程 定制键盘的输入过程

考研408笔记之数据结构(五)——图

数据结构&#xff08;五&#xff09;——图 1. 图的基本概念 1.1 图的定义 1.2 有向图和无向图 在有向图中&#xff0c;使用圆括号表示一条边&#xff0c;圆括号里元素位置互换没有影响。 在无向图中&#xff0c;使用尖括号表示一条边&#xff0c;尖括号里元素位置互换则表示…

71,【3】buuctf web [HITCON 2017]SSRFme

进入靶场 左上角是IP地址&#xff0c;下面有一堆代码 <?php // 检查是否存在 HTTP_X_FORWARDED_FOR 头部信息 if (isset($_SERVER[HTTP_X_FORWARDED_FOR])) {// 如果存在&#xff0c;将其按逗号分隔&#xff0c;并将第一个元素作为新的 REMOTE_ADDR$http_x_headers explo…

【TypeScript】模块化和命名空间、类型查找、类型缩小

模块化和命名空间 ts 在模块化中遵循 esm 规范&#xff0c;而且推荐导入类型时前面加上 type 字段&#xff0c;这些可以让一个非TypeScript编译器比如Babel、swc或者esbuild知道什么样的导入可以被安全移除。 TypeScript有它自己的模块格式&#xff0c;名为namespaces&#x…

城市生命线安全保障:技术应用与策略创新

城市生命线工程是维系城市正常运行、满足群众生产生活需要的重要基础设施。随着城市化进程的加快&#xff0c;城市基础设施生命线安全运行的复杂性日益加剧&#xff0c;保障城市居民日常生活正常运行的水、电、气、热等各类地下管线以及桥梁、市政设施、轨道交通等城市基础设施…

MVCC在MySQL中实现无锁的原理

一&#xff1a;基础知识 我们知道MySQL是多线程并发处理任务的。MySQL使用了MVCC来实现事务并发的无锁机制。 而且我们还需要知道MySQL的四种隔离级别&#xff1a;读未提交&#xff0c;读已提交&#xff08;RC&#xff09;&#xff0c;可重复读&#xff08;RR&#xff09;&am…

WPF实战案例 | C# WPF实现大学选课系统

WPF实战案例 | C# WPF实现大学选课系统 一、设计来源1.1 主界面1.2 登录界面1.3 新增课程界面1.4 修改密码界面 二、效果和源码2.1 界面设计&#xff08;XAML&#xff09;2.2 代码逻辑&#xff08;C#&#xff09; 源码下载更多优质源码分享 作者&#xff1a;xcLeigh 文章地址&a…

HTML5 Canvas和JavaScript的3D粒子星系效果

HTML部分 基本结构包括<html>, <head>, 和 <body>标签。<title>标签设置了页面标题为“优化版3D粒子星系”。<style>块定义了一些基本样式&#xff1a; body&#xff1a;无边距&#xff0c;隐藏滚动条&#xff0c;黑色背景&#xff0c;禁用触摸…

再见 Crontab!Linux 定时任务的新选择!

引言 说到 Linux 下定时执行任务&#xff0c;大多数人可能会想到 crontab&#xff1f;没错&#xff0c;它的确是 Linux 下比较通用和方便的方式&#xff0c;但是今天我来介绍一种新的方法来创建定时任务并且支持更多更强大的功能。 Systemd 很多小伙伴应该听说过 Systemd&…

Unity入门1

安装之后无法获得许可证&#xff0c;可以考虑重装 新建项目 单击空白处生成脚本 双击c#文件 会自动打开vstudio 检查引用 如果没有引用&#xff0c;重开vstu&#xff0c;或者重新加载项目 hierarchy层级 scenes场景 assets资产 inspector督察 icon图标 资源链接&…

【二叉树】遍历总结!

在很多问题中&#xff0c;熟练掌握二叉树的遍历方法&#xff0c;能够轻松解决很多问题。 新建一棵二叉树root[1,null,2,3] 1、前序遍历 前序遍历的顺序为根节点->左子树->右子树&#xff0c;按照以上二叉树&#xff0c;遍历顺序为[1&#xff0c;2&#xff0c;3]。代码为…

(2)STM32 USB设备开发-USB虚拟串口

例程&#xff1a;STM32USBdevice: 基于STM32的USB设备例子程序 - Gitee.com 本篇为USB虚拟串口教程&#xff0c;没有知识&#xff0c;全是实操&#xff0c;按照步骤就能获得一个STM32的USB虚拟串口。本例子是在野火F103MINI开发板上验证的&#xff0c;如果代码中出现一些外设的…

ASP .NET Core 学习(.NET9)部署(一)windows

在windows部署 ASP .NET Core 的时候IIS是不二选择 一、IIS安装 不论是在window7 、w10还是Windows Server&#xff0c;都是十分简单的&#xff0c;下面以Windows10为例 打开控制面版—程序—启用或关闭Windows功能 勾选图中的两项&#xff0c;其中的子项看需求自行勾选&am…

Java并发编程面试题:线程池Fork/Join(19题)

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

fyne 选项卡设计

用户界面的设计至关重要&#xff0c;它直接影响着用户体验。选卡设计作为一种常见的界面布局方式&#xff0c;能够有效地组织和展示信息&#xff0c;使用户能够方便快捷地浏览和操作。 Fyne 是一个用 Go 语言编写的跨平台 GUI 框架&#xff0c;它提供了丰富的组件和功能&#…

MySQL——主从同步

提醒&#xff1a;进行配置时&#xff0c;需要确保一主两从的操作系统、MySQL版本一致&#xff0c;否则将出现问题 环境介绍 服务器IP主服务器172.25.254.10从服务器-1172.25.254.11从服务器-2172.25.254.12 配置 # 快速配置&#xff0c;选择多重执行&#xff0c;确保版本一…