「30天自制操作系统」Day18 - dir 命令

控制光标闪烁

我们已经有多个窗口了,每个窗口有一个光标,但现在所有的光标都在闪烁。现在我们想要实现:只有可以接受键盘输入的窗口有光标闪烁,其他窗口不显示光标。

首先处理一下任务 A 的窗口,我们将按下 Tab 键时的处理以及光标闪烁的处理改写了一下,当不想显示光标的时候,使 cursor_c 为负值。这样任务 A 的窗口中的光标就可以停止闪烁了。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
for (;;) {
if (fifo32_status(&fifo) == 0) {
} else {
if (256 <= i && i <= 511) {
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);
} 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; /* 显示光标 */
}
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);
} else if (512 <= i && i <= 767) {
} else if (i <= 1) { /* 光标用定时器 */
if (i != 0) {
timer_init(timer, &fifo, 0);
if (cursor_c >= 0) {
cursor_c = COL8_000000;
}
} else {
timer_init(timer, &fifo, 1);
if (cursor_c >= 0) {
cursor_c = COL8_FFFFFF;
}
}
timer_settime(timer, 50);
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);
}
}
}
}

接下来我们实现命令行窗口的光标闪烁的控制,我们需要在 HariMain 和 console_task 之间传递信息,考虑使用 FIFO 来实现。将光标开始闪烁定义为 2,停止闪烁定义为 3。修改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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); /* 命令行窗口光标 ON */
} 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); /* 命令行窗口光标 OFF */
}
sheet_refresh(sht_win, 0, 0, sht_win->bxsize, 21);
sheet_refresh(sht_cons, 0, 0, sht_cons->bxsize, 21);
}

我们再对 console_task 进行修改,与之前对 HariMain 的修改十分类似。看一下效果吧:

对回车键的支持

按下回车键时,为了产生响应,我们向命令行窗口发送 256 + 10 这个值,代码如下:

1
2
3
4
5
if (i == 256 + 0x1c) {	/* Enter */
if (key_to != 0) { /* 发送至命令行窗口 */
fifo32_put(&task_cons->fifo, 10 + 256);
}
}

接下来我们修改 console_task,之前我们有一个 cursor_x,现在我们添加一个 cursor_y,每次按下回车时将其加 1,就能实现换行的效果了。修改后的部分代码如下:

1
2
3
4
5
6
7
8
9
10
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;
}

我们运行一下看看效果:

对窗口滚动的支持

在刚刚的程序中,当我们输入到最后一行时,回车键就不管用了,为了解决这个问题,我们要实现窗口滚动。

所谓的窗口滚动,其实就是把当前所有的像素向上移动一行,然后最下面一行涂黑就好了。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;

看一下效果:

mem 命令

mem 命令用来显示内存使用情况,为了处理我们输入的字符,我们添加了一个 cmdline 变量,用来记录通过键盘输入的内容,也就是将我们输入的一般字符顺次累积起来即可。

同时,我们把处理回车键的部分拿出来作为一个新的函数,实现换行和自动滚动的功能,这样以后调用就方便多了。该部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int cons_newline(int cursor_y, struct SHEET *sheet)
{
int x, y;
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);
}
return cursor_y;
}

这样在我们按下回车键时。先执行 cons_newline 来换行,然后读取变量 comline 的值,如果输入的内容为 mem,那么我们显示内存的相关信息;如果不是 mem,我们显示 「Bad command」;如果什么都不输入,按下回车键我们什么也不显示。

为了显示内存的相关信息,我们添加了 memtotal 和 memman 两个变量,其中 memtotal 从 HariMain 传递过来,修改 HariMain 如下:

1
2
3
4
sht_cons = sheet_alloc(shtctl);
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;

现在我们看一下效果:

cls 命令

cls 命令用于清除屏幕上的内容,因此实现非常简单,代码如下:

1
2
3
4
5
6
7
8
9
if (strcmp(cmdline, "cls") == 0) {
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;
}

现在我们输入 cls 就可以清屏了。

dir 命令

dir 命令要显示文件名等信息,我们需要读取磁盘的内容。之前我们提到过,我们读出的数据存放在内存的 0x00100000 ~ 0x00267fff 中,文件名存放在 0 柱面、0 磁头、1 扇区开始的 0x002600 之后,也就是从 0x00102600 开始写入。

我们向磁盘映像中加入 haribote.sys、ipl10.nas、make.bat 3 个文件试验一下。修改 Makefile 如下:

1
2
3
4
5
6
7
haribote.img : ipl10.bin haribote.sys Makefile
$(EDIMG) imgin:../z_tools/fdimg0at.tek \
wbinimg src:ipl10.bin len:512 from:0 to:0 \
copy from:haribote.sys to:@: \
copy from:ipl10.nas to:@: \
copy from:make.bat to:@: \
imgout:haribote.img

发现用 wine 的情况下添加 make.bat 会产生 readfile error… 在 Windows 7 虚拟机环境下运行正常。

我们运行一下,查看磁盘映像中 0x002600 字节后的部分,如下图所示:

内容以 32 个字节为单位循环,这 32 个字节结构如下:

1
2
3
4
5
6
struct FILEINFO {
unsigned char name[8], ext[3], type;
char reserve[10];
unsigned short time, date, clustno;
unsigned int size;
};

开始的 8 个字节 name 是文件名(不足 8 个字节时用空格补足);如果文件名的第一个字节为 0xe5,代表这个文件已被删除,如果第一个字节为 0x00,代表着一段不包含任何文件名信息。

接下来 3 个字节 ext 是扩展名(不足 3 个字节时用空格补足),和文件名一样全部使用大写字母。

后面一个字节 type 存放文件的属性信息,具体含义如下表所示:

type 含义
0x01 只读文件(不可写入)
0x02 隐藏文件
0x04 系统文件
0x08 非文件信息(比如磁盘名称等)
0x10 目录

当一个文件既是只读文件又是隐藏文件时,把对应 type 的值相加即可,即 0x03。

接下来 10 个字节 reserve 为保留,在磁盘映像中为 0x00。

下面 2 个字节 time 为 WORD 整数,存放文件的时间,date 为存放文件的日期,最后的 clustno 代表这个文件的内容从磁盘上的哪个扇区开始存放。

最后 4 个字节 size 表示存放文件的大小。

dir 命令的实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct FILEINFO *finfo = (struct FILEINFO *) (ADR_DISKIMG + 0x002600);

if (strcmp(cmdline, "dir") == 0) {
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);
}

我们来看一下效果:

显示出了我们载入的 3 个文件,这样我们就成功了~