本篇的内容比较简单。首先进一步优化命令行窗口的功能,增加回车键与窗口滚动,然后实现几个主要命令mem、cls和dir。
1. 命令行窗口优化
本篇内容的开始,先对上一篇完成的命令行窗口做一些优化。
首先是命令行窗口光标的控制。Windows中的命令行窗口,在没有切换到命令行窗口的时候光标是不闪烁的,这里我们加上这一优化。
……
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);
cursor_c = -1; /* 不显示光标 */
boxfill8(sht_win->buf, sht_win->bxsize, COL8_FFFFFF, cursor_x, 28, cursor_x + 7, 43);
fifo32_put(&task_cons->fifo, 2); /* 传送给命令行窗口,2表示开启光标闪烁 */
} else {
key_to = 0;
make_wtitle8(buf_win, sht_win->bxsize, "task_a", 1);
make_wtitle8(buf_cons, sht_cons->bxsize, "console", 0);
cursor_c = COL8_000000; /* 显示光标 */
fifo32_put(&task_cons->fifo, 3); /* 传送给命令行窗口,3表示关闭光标闪烁 */
}
sheet_refresh(sht_win, 0, 0, sht_win->bxsize, 21);
sheet_refresh(sht_cons, 0, 0, sht_cons->bxsize, 21);
}
……
/* 重新显示光标 */
if (cursor_c >= 0) {
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);
……
对于任务A,在不需要显示光标的时候将cursor_c设置为-1;而对于命令行窗口,需要写入FIFO,定义2为开启光标显示,3为关闭光标显示。在console_task中,在收到FIFO中的数据为3时,同样将cursor_c置为-1:
……
int i, cursor_c = -1;
……
if (i == 2)
{ /* 开启光标显示 */
cursor_c = COL8_FFFFFF;
}
if (i == 3)
{ /* 关闭光标显示 */
boxfill8(sheet->buf, sheet->bxsize, COL8_000000, cursor_x, 28, cursor_x + 7, 43);
cursor_c = -1;
}
这样在命令行不输入时,就不显示光标:
当前的命令行窗口只能输入一行,所以接下来加入对回车键的支持。
先考虑简单的情况,在不考虑有其他命令输入后按回车的情况下,我们只需要在按下回车之后进行换行。
首先修改主程序,在按下回车键时向命令行窗口发送10+256这个值(10是回车键的ASCII码值,当然使用其他值也是可以的)。
……
if (i == 256 + 0x1c) { /* 回车键 */
if (key_to != 0) { /* 发送到命令行窗口 */
fifo32_put(&task_cons->fifo, 10 + 256);
}
}
……
然后修改接收数据的console_task。在console_task中已经定义了cursor_x变量,接下来需要定义cursor_y变量,在按下回车键时将变量增加一行的高度即可。
……
else if (i == 10 + 256) {
/* Enter */
if (cursor_y < 28 + 112) {
/* 用空格将光标擦除 */
putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, " ", 1);
cursor_y += 16;
/* 显示提示符 */
putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, ">", 1);
cursor_x = 16;
}
}
……
/* 重新显示提示符 */
if (cursor_c >= 0) {
boxfill8(sheet->buf, sheet->bxsize, cursor_c, cursor_x, cursor_y, cursor_x + 7, cursor_y + 15);
}
sheet_refresh(sheet, cursor_x, cursor_y, cursor_x + 8, cursor_y + 16);
……
这样就可以在命令行窗口中使用回车键了。
既然支持了回车,那么显示到窗口的最后一行,我们还应该支持滚动。实现起来同样很简单,只要将当前窗口中的所有像素向上移动一行即可。同时要把最后一行擦除,否则还会显示上一行的残留。
……
else if (i == 10 + 256)
{
/* Enter */
putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, " ", 1);
if (cursor_y < 28 + 112) {
cursor_y += 16; /* 换行 */
} else {
/* 滚动 */
for (y = 28; y < 28 + 112; y++) {
for (x = 8; x < 8 + 240; x++) {
sheet->buf[x + y * sheet->bxsize] = sheet->buf[x + (y + 16) * sheet->bxsize];
}
}
for (y = 28 + 112; y < 28 + 128; y++) {
for (x = 8; x < 8 + 240; x++) {
sheet->buf[x + y * sheet->bxsize] = COL8_000000;
}
}
sheet_refresh(sheet, 8, 28, 8 + 240, 28 + 128);
}
/* 显示提示符 */
putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, ">", 1);
cursor_x = 16;
……
这样在到达命令行底部时,就可以继续向下滚动了。
另外终于解决了之前的一个问题。发现窗口输入需要先按一下shift,再输入,这样才能正常输入字符,否则就会出现按下一个键就重复输出同一个字符的问题。但具体原因还不清楚,也许是QEMU软件的问题?
2. 几个命令的支持
2.1 mem命令
命令行窗口优化的差不多了,接下来我们来实现几个命令。
首先是mem命令,用来获取内存使用情况的信息。当前我们将内存信息显示在了窗口上,其实通过命令来查询更为合理一些。这样我们就把之前显示在桌面上的内存信息、按键编码等信息一起去掉了,只需将相关代码注释掉即可。
接下来在console_task中增加mem命令:
void console_task(struct SHEET *sheet, unsigned int memtotal)
{
struct TIMER *timer;
struct TASK *task = task_now();
int i, fifobuf[128], cursor_x = 16, cursor_y = 28, cursor_c = -1;
// 初始化存放命令的数组
char s[30], cmdline[30];
// 内存信息
struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
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);
if (cursor_c >= 0) {
cursor_c = COL8_FFFFFF;
}
} else {
timer_init(timer, &task->fifo, 1);
if (cursor_c >= 0) {
cursor_c = COL8_000000;
}
}
timer_settime(timer, 50);
}
if (i == 2) {
cursor_c = COL8_FFFFFF;
}
if (i == 3) {
boxfill8(sheet->buf, sheet->bxsize, COL8_000000, cursor_x, cursor_y, cursor_x + 7, cursor_y + 15);
cursor_c = -1;
}
if (256 <= i && i <= 511) {
if (i == 8 + 256) {
if (cursor_x > 16) {/
putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, " ", 1);
cursor_x -= 8;
}
} else if (i == 10 + 256) {
/* Enter */
/* 将光标用空格键擦除后换行 */
putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, " ", 1);
cmdline[cursor_x / 8 - 2] = 0;
cursor_y = cons_newline(cursor_y, sheet);
/* 执行命令 */
if (cmdline[0] == 'm' && cmdline[1] == 'e' && cmdline[2] == 'm' && cmdline[3] == 0) {
/* mem命令 */
sprintf(s, "total %dMB", memtotal / (1024 * 1024));
putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
cursor_y = cons_newline(cursor_y, sheet);
sprintf(s, "free %dKB", memman_total(memman) / 1024);
putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
cursor_y = cons_newline(cursor_y, sheet);
cursor_y = cons_newline(cursor_y, sheet);
} else if (cmdline[0] != 0) {
/* 不是命令,也不是空行 */
putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, "Bad command.", 12);
cursor_y = cons_newline(cursor_y, sheet);
cursor_y = cons_newline(cursor_y, sheet);
}
/* 显示提示符*/
putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, ">", 1);
cursor_x = 16;
} else {
if (cursor_x < 240) {
s[0] = i - 256;
s[1] = 0;
cmdline[cursor_x / 8 - 2] = i - 256;
putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, s, 1);
cursor_x += 8;
}
}
}
if (cursor_c >= 0) {
boxfill8(sheet->buf, sheet->bxsize, cursor_c, cursor_x, cursor_y, cursor_x + 7, cursor_y + 15);
}
sheet_refresh(sheet, cursor_x, cursor_y, cursor_x + 8, cursor_y + 16);
}
}
}
代码看起来也比较清楚。在console_task中添加了一个cmdline变量用于记录键盘输入的内容,而memtotal和memman两个变量用于mem命令。memtotal变量需要通过主程序传送过来:
……
task_cons->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 12;
……
*((int *) (task_cons->tss.esp + 4)) = (int) sht_cons;
*((int *) (task_cons->tss.esp + 8)) = memtotal;
……
处理回车键输入的部分,改写为一个函数cons_newline:
int cons_newline(int cursor_y, struct SHEET *sheet)
{
int x, y;
if (cursor_y < 28 + 112) {
cursor_y += 16; /* ���̍s�� */
} else {
/* �X�N���[�� */
for (y = 28; y < 28 + 112; y++) {
for (x = 8; x < 8 + 240; x++) {
sheet->buf[x + y * sheet->bxsize] = sheet->buf[x + (y + 16) * sheet->bxsize];
}
}
for (y = 28 + 112; y < 28 + 128; y++) {
for (x = 8; x < 8 + 240; x++) {
sheet->buf[x + y * sheet->bxsize] = COL8_000000;
}
}
sheet_refresh(sheet, 8, 28, 8 + 240, 28 + 128);
}
return cursor_y;
}
当按下回车键时,执行程序后会读取cmdline中的内容。如果是“mem”,则显示内存相关的信息,否则显示“Bad command”错误信息。
2.2 cls命令
这个命令是Windows中用于清除屏幕内容的。有了上面mem命令的基础,只需要把cls命令添加进去。
……
if (strcmp(cmdline, "mem") == 0)
{
/* mem命令 */
sprintf(s, "total %dMB", memtotal / (1024 * 1024));
putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
cursor_y = cons_newline(cursor_y, sheet);
sprintf(s, "free %dKB", memman_total(memman) / 1024);
putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
cursor_y = cons_newline(cursor_y, sheet);
cursor_y = cons_newline(cursor_y, sheet);
}
else if (strcmp(cmdline, "cls") == 0)
{
/* cls命令 */
for (y = 28; y < 28 + 128; y++) {
for (x = 8; x < 8 + 240; x++) {
sheet->buf[x + y * sheet->bxsize] = COL8_000000;
}
}
sheet_refresh(sheet, 8, 28, 8 + 240, 28 + 128);
cursor_y = 28;
}
else if (cmdline[0] != 0)
{
putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, "Bad command.", 12);
cursor_y = cons_newline(cursor_y, sheet);
cursor_y = cons_newline(cursor_y, sheet);
}
cls命令的实现很简单,只需要将命令行当前窗口除去第一个提示符外的部分都显示为背景色即可。这里使用strcmp函数替代了之前的单个字符比较。
2.3 dir命令
dir命令是Windows中用于显示文件信息的命令,对应Linux下的ls命令。为了显示文件信息,我们就需要读取磁盘的内容了。这里我们先简化一下,利用之前从磁盘中读出并存放在内存中的部分0x00100000~0x00267ff。(参考【读书笔记-《30天自制操作系统》-7】Day8)
通过二进制编辑器打开磁盘镜像文件,可以看到文件的信息存放在0x002600以后,以32个字节为单位循环。
struct FILEINFO
{
unsigned char name[8], ext[3], type;
char reserver [10];
unsigned short time, data, clustno;
unsigned int size;
};
开始的8个字节为文件名,不足8个字节的用空格补全。因为8个字节以上的文件名较为复杂,当前我们暂时不予考虑,从简单的情况入手。如果文件名的第一个字节是0xe5,表示这个文件已经被删除了;0x00表示这一段不包含任何文件信息。
下面3个字节是文件的扩展名,同样是不足3个字节用空格补全。
后面一个字节是属性信息:
- 0x01: 只读文件
- 0x02: 隐藏文件
- 0x03: 非文件信息(如磁盘名称等)
- 0x10: 目录
如果一个文件既是只读文件,又是隐藏文件,只需要将以上的对应值相加。
了解了这些信息,来实现dir命令。
#define ADR_BOOTINFO 0x00000ff0
#define ADR_DISKIMG 0x00100000
struct FILEINFO
{
unsigned char name[8], ext[3], type;
char reserve[10];
unsigned short time, date, clustno;
unsigned int size;
};
……
if (strcmp(cmdline, "mem") == 0)
{
/* mem命令 */
sprintf(s, "total %dMB", memtotal / (1024 * 1024));
putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
cursor_y = cons_newline(cursor_y, sheet);
sprintf(s, "free %dKB", memman_total(memman) / 1024);
putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
cursor_y = cons_newline(cursor_y, sheet);
cursor_y = cons_newline(cursor_y, sheet);
}
else if (strcmp(cmdline, "cls") == 0)
{
/* cls命令 */
for (y = 28; y < 28 + 128; y++) {
for (x = 8; x < 8 + 240; x++) {
sheet->buf[x + y * sheet->bxsize] = COL8_000000;
}
}
sheet_refresh(sheet, 8, 28, 8 + 240, 28 + 128);
cursor_y = 28;
}
else if (strcmp(cmdline, "dir") == 0)
{
/* dir命令 */
for (x = 0; x < 224; x++) {
if (finfo[x].name[0] == 0x00) {
break;
}
if (finfo[x].name[0] != 0xe5) {
if ((finfo[x].type & 0x18) == 0) {
sprintf(s, "filename.ext %7d", finfo[x].size);
for (y = 0; y < 8; y++) {
s[y] = finfo[x].name[y];
}
s[ 9] = finfo[x].ext[0];
s[10] = finfo[x].ext[1];
s[11] = finfo[x].ext[2];
putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
cursor_y = cons_newline(cursor_y, sheet);
}
}
}
cursor_y = cons_newline(cursor_y, sheet);
}
else if (cmdline[0] != 0)
{
putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, "Bad command.", 12);
cursor_y = cons_newline(cursor_y, sheet);
cursor_y = cons_newline(cursor_y, sheet);
}
……
可以看到程序中获取了文件的文件名,扩展名与文件大小。
从下一篇开始,就要进入应用程序的开发了。毕竟一个操作系统最终要能运行应用程序。敬请期待。