《30天自制操作系统》17_day_学习笔记
harib14a:
接着上一天的内容,我们来思考一个问题:当所有的LEVEL中都没有任务的时候怎么办?采用前面定时器链表中加入“哨兵”的思路,我们加入一个idle任务,去执行HLT。接下来我们只需要将这个闲置任务idle放在最下层的LEVEL就行了。之后修改一下HariMain测试一下结果。
void task_idle(void)//限制任务idle { for (;;) { io_hlt();//让CPU不断的HLT等待中断的到来 } } struct TASK *task_init(struct MEMMAN *memman) { struct TASK *task, *idle; //..... idle = task_alloc();//为dile分配空间 idle->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024; idle->tss.eip = (int) &task_idle;//dile任务做的事,执行这个函数 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; //idle放在最后一层,FLAG置1 task_run(idle, MAX_TASKLEVELS - 1, 1); return task; }
harib14b:
这里我们尝试创建一个命令行窗口。首先对任务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);/* 设置缓冲区,将CMD窗口图层写到缓冲区中 */ make_window8(buf_cons, 256, 165, "console", 0);//用CMD缓冲区的内容画窗口 make_textbox8(sht_cons, 8, 28, 240, 128, COL8_000000);//CMD图层下的文本框 task_cons = task_alloc(); //CMD窗口的任务、分配空间 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;//将CMD图层地址放到CMD任务块首部 task_run(task_cons, 2, 2); /* 设置LEVEL和优先级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); //..... } 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); /* i=1;此时重新设置光标定时器,写入FIFO中改为0 */ cursor_c = COL8_FFFFFF; //黑色背景 } else { timer_init(timer, &fifo, 1); /* i=1;此时重新设置光标定时器,写入FIFO中改为0 */ cursor_c = COL8_000000; //白色背景 } timer_settime(timer, 50); //50个count计数的时间、文本显示框 boxfill8(sheet->buf, sheet->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43); sheet_refresh(sheet, cursor_x, 28, cursor_x + 8, 44);//刷新显示 } } } }
harib14c:
到这里我们已经有两个窗口了,下面我们一步一步实现按下TAB的时候切换输入的窗口的功能,这里我们先修改窗口标题栏的颜色
1、改写make_windows8()
//我们将描绘窗口标题栏的代码和描绘窗口文本框的代码区分开来 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); //第二步:调用这个函数,将缓存中的内容写到VRAM中 make_wtitle8(buf, xsize, title, act); return; } void make_wtitle8(unsigned char *buf, int xsize, char *title, char act) { //这里是描绘窗口标题栏的代码,这里没有什么可讲的。 //就是把两部分代码区分开,便于设置标题的颜色(用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; }
2、修改HariMain
//加入TAB键的判断,调用make_wtitle8并修改两个窗口标题的颜色 void HariMain(void) { //.....i为从FIFO中获得中断的数据 if (256 <= i && i <= 511) { /* 如果获得的是键盘中断的数据 */ //...... if (i == 256 + 0x0f) { /* 键盘中断的键值是TAB键 */ if (key_to == 0) { //这里的原理和光标闪烁是一样的 key_to = 1; //发送到CMD窗口 make_wtitle8(buf_win, sht_win->bxsize, "task_a", 0); make_wtitle8(buf_cons, sht_cons->bxsize, "console", 1); } else { key_to = 0; //发送到任务A make_wtitle8(buf_win, sht_win->bxsize, "task_a", 1); make_wtitle8(buf_cons, sht_cons->bxsize, "console", 0); } //接着刷新CMD和窗口图层 sheet_refresh(sht_win, 0, 0, sht_win->bxsize, 21); sheet_refresh(sht_cons, 0, 0, sht_cons->bxsize, 21); } } //..... }
harib14d:
我们虽然让窗口的颜色从灰色变成了蓝色,下面我们实现向CMD窗口中输入字符串。只要在键盘被按下的时候,往CMD任务的FIFO中发送数据即可。考虑到很多任务都需要用到缓冲区,我们将FIFO加到任务结构体TASK中。
1、将FIFO加到任务结构体TASK中。
struct TASK { int sel, flags; /* sel表示GDT的编号 */ int level, priority; struct FIFO32 fifo;//任务的缓冲区 struct TSS32 tss;//任务段的内容 };
2、修改HariMain
//判断key_to的值,并向FIFO发送数据。 void HariMain(void) { //.... for (;;) { //... 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) { /* 在keytable表中找到一般字符 */ if (key_to == 0) { /* 把获得的字符发送给任务A的缓冲区 */ if (cursor_x < 128) {//窗口的最大长度128像素 /* 每输入一个字符,光标向后移动8个像素 */ s[0] = keytable[i - 256]; s[1] = 0; //在窗口(任务A)图层显示字符 putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1); cursor_x += 8; } } else {/* 发送到CMD窗口(任务)的缓冲区 */ fifo32_put(&task_cons->fifo, keytable[i - 256] + 256); } } if (i == 256 + 0x0e) { /* Backspace键 */ if (key_to == 0) { /* 发送信息到A任务 */ if (cursor_x > 8) { /* 闪烁光标向左移动8个像素 */ putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1); cursor_x -= 8; } } else { /* 发送到任务CMD窗口 */ fifo32_put(&task_cons->fifo, 8 + 256); } } //...... } } //... }
harib14e:
这里我们来实现“!”和“%”的输入,我们先来看看Shift键的编码:
按下 抬起
左Shift 0x2a 0xaa
右Shift 0x36 0xb6
因此我们准备一个key_shift变量,当左Shift按下时置为1,当右Shift按下时置为2,两个都不按时置为0,两个都按下的时候就置为3。当key_shift为0时,我们用keytable0[]进行字符编码转换,当key_shift不为0时,我们使用keytable1[]进行转换。
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 }; //..... for (;;) { //...... 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 { /* 向CMD窗口中写 */ fifo32_put(&task_cons->fifo, s[0] + 256); } } //........ 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; } //./..... } } }//..... }
harib14f:
接下来我们要实现大写、小写字母的输入,要区分字母的大小写,我们必须同时判断Shift键的状态以及CapsLock的状态。这里笔者总结出小写字母的条件:Shift和CapsLock的状态相同(同为ON或者OFF)我们从BIOS中获取CapsLock的状态
binfo->leds的第四位->ScrollLock的状态
binfo->leds的第五位->NumLock 的状态
binfo->leds的第六位->CapsLock 的状态
我们使用上述的数据和规则来处理大小写字母的输入问题:
void HariMain(void) { //......这里只取binfo->leds4-6位的数据。 int key_to = 0, key_shift = 0, key_leds = (binfo->leds >> 4) & 7; //...... if ('A' <= s[0] && s[0] <= 'Z') { /* 大写字母A-Z之间 */ if (((key_leds & 4) == 0 && key_shift == 0) || //自行在草稿纸上推算 ((key_leds & 4) != 0 && key_shift != 0)) { s[0] += 0x20; /* 字符值0x20变为小写字母的值 */ } } //...... }
harib14g:
对键盘上锁定键的支持,我们在上面已经实现了锁定键的切换,但是有时候指示灯并不进行相应的切换,下面我们来解决这个问题。先来看看常用三个锁定键的键值:
0x3a : CapsLock
0x45 : NumLock
0x46 : ScrollLock
对于键盘上锁定键LED的控制,可采用以下方法想键盘发送指令和数据;
- 读取状态寄存器,等待bit1的值变为0
- 向数据输出(0060)写入要发送的1个字节的数据
- 等待键盘返回1个字节的信息,这和等待键盘输入采用的方法相同(用IRQ等待或者用轮询状态寄存器bit1的值知道变为0都可以)
- 返回的信息如果是0xfa,表示1个字节的数据已经成功发送给键盘了。如果为0xfe则表示发送失败,返回 1 步重新发送
要控制LED的状态,需要按照上述方法执行两次,向键盘发送EDxx数据,其中,xx的bit0(ScrollLock)bit1(NumLock)bit2(CapsLock),0表示熄灭,1表示点亮。
void HariMain(void) { struct FIFO32 fifo, keycmd; int fifobuf[128], keycmd_buf[32]; //keycmd_wait表示键盘向控制器发送数据的状态。 // ==-1:通常状态可以发送指令 //!=-1:键盘控制器正在等待发送的数据(保存在keycmd_wait) //key_leds设置指示灯的状态,这个数据是要送到键盘控制器的 int key_to = 0, key_shift = 0, key_leds = (binfo->leds >> 4) & 7, keycmd_wait = -1; //初始化KEYCMD任务A想键盘发送数据的缓冲区 fifo32_put(&keycmd, KEYCMD_LED); fifo32_put(&keycmd, key_leds); //..... for (;;) { if (fifo32_status(&keycmd) > 0 && keycmd_wait < 0) { /* 如果存在想键盘控制器发送的数据,则发送它 */ keycmd_wait = fifo32_get(&keycmd);//从KEYCMD缓冲区中获得要向键盘发送的数据 wait_KBC_sendready(); //等待ACK;等待键盘准备好 io_out8(PORT_KEYDAT, keycmd_wait);//将keycmd_wait的值送到键盘控制器 } io_cli(); if (fifo32_status(&fifo) == 0) { task_sleep(task_a); //任务A中没有信息,没有从键盘接收的数据,休眠 io_sti(); } else { //任务A有信息 i = fifo32_get(&fifo);//从任务A的缓冲区得到信息 io_sti(); if (256 <= i && i <= 511) {/* 得到键盘数据 */ //..... if (i == 256 + 0x3a) { /* CapsLock */ key_leds ^= 4; //设置CapsLock指示灯亮(ley_leds的第2位为1) fifo32_put(&keycmd, KEYCMD_LED);//先送入默认的KEYCMD_LED是为了下一次获得ACK(0xfa) fifo32_put(&keycmd, key_leds); //接着再送入设置的指示灯状态信息 } if (i == 256 + 0x45) { /* NumLock */ key_leds ^= 2; //设置NumLock 指示灯亮(ley_leds的第1位为1) fifo32_put(&keycmd, KEYCMD_LED);//同上 fifo32_put(&keycmd, key_leds); } if (i == 256 + 0x46) { /* ScrollLock */ key_leds ^= 1; //设置ScrollLock 指示灯亮(ley_leds的第0位为1) fifo32_put(&keycmd, KEYCMD_LED);//同上 fifo32_put(&keycmd, key_leds); } if (i == 256 + 0xfa) { /* 键盘成功接收数据返回一个ACK(0xfa) */ keycmd_wait = -1; //这是又可以继续向键盘控制器发送数据了 } if (i == 256 + 0xfe) { /* 键盘接收数据失败返回ACK(0xfe) */ wait_KBC_sendready(); //等着(0xfa)键盘准备好 io_out8(PORT_KEYDAT, keycmd_wait);//重新把keycmd_wait的值发送到键盘控制器 } //....... } } } }