【读书笔记-《30天自制操作系统》-16】Day17

本篇内容开始进入一个新的主题——命令行,这是一个操作系统很基本的功能。本篇中首先实现命令行窗口的显示,做到能切换到窗口以及实现向窗口输入内容。接下来在之前键盘输入的基础上,增加对符号以及大小写字母的输入。最后再加入对其他锁定键的支持。
在这里插入图片描述
1. 创建命令行窗口与窗口输入

首先创建一个命令行窗口。有了之前图层相关内容的基础,创建命令行窗口其实也只是在显示画面上描绘出图案。这里我们同时显示任务A与命令行窗口:

1.1 创建命令行窗口

……
	//代码段1
	/* sht_back */
	sht_back  = sheet_alloc(shtctl);
	buf_back  = (unsigned char *) memman_alloc_4k(memman, binfo->scrnx * binfo->scrny);
	sheet_setbuf(sht_back, buf_back, binfo->scrnx, binfo->scrny, -1); 
	init_screen8(buf_back, binfo->scrnx, binfo->scrny);

	/* 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 */
……
	//代码段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);
……

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); 
					cursor_c = COL8_FFFFFF;
				} else {
					timer_init(timer, &fifo, 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);
			}
		}
	}
}

代码段1描绘了背景画面与命令行窗口等图层,并初始化命令行窗口任务;代码段2则设置好这些图层的显示位置与上下层关系。命令行窗口的任务consle_task中处理也比较简单,先是实现了光标的闪烁。运行效果如下:
在这里插入图片描述

1.2 向命令行窗口输入

当前状态下还是没有实现对命令行窗口输入,而且命令行窗口的标题栏显示还是灰色的,我们一点点进行优化。

首先实现按tab键切换到命令行窗口,并改变命令行窗口标题栏颜色的功能。

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;
}

以上函数在act为0与不为0时展示两种不同的颜色。同时在主程序处理键盘中断的部分也需要进行修改:

……
		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);
		}
……

当按下tab键时,修改key_to的值,该变量的值用于确定键盘输入发送到哪里。key_to为0时发送到任务A,为1时则发送到命令行窗口任务,同时改变对应窗口标题栏的颜色。

这样运行之后,按下tab键,console与taks_a窗口的标题栏颜色会进行切换。

在这里插入图片描述
但是此时输入仍然出现在task_a窗口中,我们需要继续优化,使键盘能够在console窗口中输入。

实现向console窗口中输入,只需要按下键盘时向console任务的FIFO中发送数据即可。目前每个任务都需要自己的FIFO,这样我们修改结构体TASK的定义,将FIFO与任务绑定。

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

接下来需要在主程序中根据key_to的值将数据写入不同的FIFO。

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);
			}
		}

不仅是普通的字符,还要注意Backspace键的处理。

if (i == 256 + 0x0e) {	/* Backspace键 */
	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);
	}
}

console_task任务也需要进行修改,使任务能够接收并处理键盘数据:

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); 
					cursor_c = COL8_FFFFFF;
				} else {
					timer_init(timer, &task->fifo, 1); 
					cursor_c = COL8_000000;
				}
				timer_settime(timer, 50);
			}
			if (256 <= i && i <= 511) { /* 
				if (i == 8 + 256) {
					/* Backspace键 */
					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);
		}
	}
}

这里与主程序的处理基本相同,增加了命令行窗口起始位置的提示符">“,并且对删除的界限做了限制,以防提示符被删除。运行结果如下:
在这里插入图片描述
好了,当前可用向命令行窗口中输入数字、字母和一些符号了,但还无法输入”!"与“%”,下面来解决这个问题。

2. 输入符号和大小写字母

要输入“!”和“%”,我们首先要对Shift键进行处理。

  • 左Shift:按下:0x2a 抬起:0xaa
  • 右Shift:按下:0x35 抬起:0xb6

我们准备一个key_shift变量,当左Shift按下时置为1,右Shift按下时置为2,同时按下置为3,都没有按下置为0;
准备两个keytable表,当key_shift为0时,用keytable0[]将按键编码转换为字符编码;当key_shift不为0时,使用keytbale[]进行转换。

	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) {	/* Backspace键 */
					if (key_to == 0) {	
						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);
				}
				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;
				}
				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) { 
				if (mouse_decode(&mdec, i - 512) != 0) {
					sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y);
					if ((mdec.btn & 0x01) != 0) {
						s[1] = 'L';
					}
					if ((mdec.btn & 0x02) != 0) {
						s[3] = 'R';
					}
					if ((mdec.btn & 0x04) != 0) {
						s[2] = 'C';
					}
					putfonts8_asc_sht(sht_back, 32, 16, COL8_FFFFFF, COL8_008484, s, 15);
					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;
					}
					sprintf(s, "(%3d, %3d)", mx, my);
					putfonts8_asc_sht(sht_back, 0, 0, COL8_FFFFFF, COL8_008484, s, 10);
					sheet_slide(sht_mouse, mx, my);
					if ((mdec.btn & 0x01) != 0) {
						sheet_slide(sht_win, mx - 80, my - 8);
					}
				}
			} else if (i <= 1) {
				if (i != 0) {
					timer_init(timer, &fifo, 0); 
					cursor_c = COL8_000000;
				} else {
					timer_init(timer, &fifo, 1); 
					cursor_c = COL8_FFFFFF;
				}
				timer_settime(timer, 50);
				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);
			}
		}
	}
}

这样就实现了一些特殊字符的输入:
在这里插入图片描述
不过到目前位置我们输入的都只是大写字母,如何输入小写字母呢?

要输入小写字母,我们需要同时判断Shift与CapsLock的状态。

输入小写字母的条件,总结下来就是

  • 输入的字符为英文字母
  • “CapsLock为Off & Shift键为Off”或“CapsLock为On & Shift键为On”

CapsLock的状态我们已经从BIOS获取并且保存起来了,现在只需要使用即可。

  • binfo->leds的第4位: ScrollLock状态
  • binfo->leds的第5位: NumLock状态
  • binfo->leds的第6位:CapsLock状态

这样我们就可以支持小写字母的输入了:

……

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;	/*将大写字母转换为小写字母 */
	}
}

……

在我们使用的ASCII码中,只要将大写字母的编码加上0x20即可得到小写字母的编码。

在这里插入图片描述
既然已经提到了这些锁定键,接下来就更进一步,实现对这些锁定键的支持吧。

3. 支持锁定键

已经知道了这些锁定键的按键编码,接收到这些编码时只需要将binfo->leds中的相应内容改写就可以了。
但是目前还没有实现与键盘上控制灯的同步,可能会出现键盘上的CapsLock灯没有亮起,却处于CapsLock模式,这个问题还是需要解决一下。

关于NumLock与CapsLock等LED的控制,可以采用如下的方法向键盘发送指令和数据:

  • 读取状态寄存器,等待bit1的值变为0
  • 向数据输出(0060)写入要发送的一个字节数据
  • 等待键盘返回一个字节的信息,与等待键盘输入所采用的方法相同(IRQ等待或者用轮询状态寄存器bit1的值直到其变为0)
  • 返回的信息如果为0xfa,表明一个字节的数据已经成功发送给键盘,如果为0xfe则表明发送失败,需要返回第一步重新发送

要控制LED的状态,需要按上述方法执行两次,向键盘发送EDxx数据。其中xx的bit0代表ScrollLock,bit1代表NumLock,bit2代表CapsLock(0表示熄灭,1表示点亮),bit3-7为保留位,置为0即可。

根据以上信息对程序进行修改:

……

	/* 为了避免和键盘当前状态冲突,在一开始先进行设置 */
	fifo32_put(&keycmd, KEYCMD_LED);
	fifo32_put(&keycmd, key_leds);

……

if (fifo32_status(&keycmd) > 0 && keycmd_wait < 0) {
			/* 如果存在向键盘控制器发送的数据,则发送 */
			keycmd_wait = fifo32_get(&keycmd);
			wait_KBC_sendready();
			io_out8(PORT_KEYDAT, keycmd_wait);
		}

……

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);
				}

这里创建了一个keycmd的FIFO缓冲区,用来管理任务A向键盘控制器发送数据的顺序。如果有数据要发送给键盘控制器,会先在keycmd中累积起来。

keycmd_wait变量用来表示向键盘控制器发送数据的状态。当keycmd_wai的值为-1时,表示键盘控制器处于通常状态,可以发送指令;当值不为-1时,表示键盘控制器正在等待发送的数据,这是要被发送的数据被保存在keycmd_wait变量中。

在for循环的开头,当keycmd中有数据且keycmd_wait为-1时,向键盘发送一个字节的数据,在开始发送数据的同时,keycmd_wait变为非-1的值。随后,当从键盘接收到0xfa的返回值时,keycmd_wait恢复为-1,继续发送下一个数据。当键盘接收到的返回值为0xfe时,则重新发送刚才的数据。

这样锁定键的支持就完成了。但实际通过虚拟机运行还是会与设计不符。作者在真机上运行是正常的,推测可能原因是虚拟机的键盘灯仍然是由Windows控制的,而不是虚拟机控制的。

本篇内容比较简单,也更为可视化,算是学习多任务之后的一个调剂吧。下一篇继续命令行窗口,敬请期待。

  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值