本篇内容涉及到文件与文件系统,以及应用程序的运行。首先实现type命令,读取文件并显示;接下来导入对FAT文件系统的支持,实现读取大小512字节以上,存放在不连续扇区中的文件。在此基础上,最终实现读取并运行应用程序。
1. type命令实现
type命令是Windows命令行中用于读取并显示文件内容的命令,对应Linux中的cat命令。为了获取文件信息,这里还是要用到上一篇提到的结构体:
struct FILEINFO {unsigned char name[8], ext[3], type;char reserve[10];unsigned short time, date, clustno;unsigned int size;
};
其中clustno这个成员表示文件从磁盘上的哪个扇区开始存放。对于当前的操作系统映像,文件的地址有如下公式:
地址 = clustno * 512 + 0x003e00
这样只需将文件的内容读取,并显示出来即可:
void console_task(struct SHEET *sheet, unsigned int memtotal)
{……char s[30], cmdline[30], *p;……for (;;) {io_cli();if (fifo32_status(&task->fifo) == 0) {task_sleep(task);io_sti();} else {……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 (strcmp(cmdline, "mem") == 0) {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) {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) {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] == 't' && cmdline[1] == 'y' && cmdline[2] == 'p' &&cmdline[3] == 'e' && cmdline[4] == ' ') {/* type命令 *//* 准备文件名 */for (y = 0; y < 11; y++) {s[y] = ' ';}y = 0;for (x = 5; y < 11 && cmdline[x] != 0; x++) {if (cmdline[x] == '.' && y <= 8) {y = 8;} else {s[y] = cmdline[x];if ('a' <= s[y] && s[y] <= 'z') {/* 将小写字母转换为大写字母 */s[y] -= 0x20;} y++;}}/* 寻找文件 */for (x = 0; x < 224; ) {if (finfo[x].name[0] == 0x00) {break;}if ((finfo[x].type & 0x18) == 0) {for (y = 0; y < 11; y++) {if (finfo[x].name[y] != s[y]) {goto type_next_file;}}break; /* 找到文件 */}type_next_file:x++;}if (x < 224 && finfo[x].name[0] != 0x00) {/* 找到文件的情况 */y = finfo[x].size;p = (char *) (finfo[x].clustno * 512 + 0x003e00 + ADR_DISKIMG);cursor_x = 8;for (x = 0; x < y; x++) {/* 逐个字符输出 */s[0] = p[x];s[1] = 0;putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, s, 1);cursor_x += 8;if (cursor_x == 8 + 240) {/* 到达最右端后换行*/cursor_x = 8;cursor_y = cons_newline(cursor_y, sheet);}}} else {/* 没有找到文件的情况 */putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, "File not found.", 15);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);}}
}
因为type命令后面会跟文件名,因此这里只比较前4个字符是否为“type”。而在准备文件名部分,会将命令行输入的文件名存入s[]数组中,并转换为大写字母。当前只考虑8个字节的文件名与3个字节的扩展名,因此数组的长度为11。找到文件名后,则将文件的内容逐个字符显示出来,否则显示"File not found"。
尝试用type命令打卡make.bat文件,显示很正常:
而尝试打开二进制文件时,则会显示乱码:
再尝试打开nas汇编代码文件时,效果如下:
可以看到有些字符如"DB","0x55"正常显示出来了,但还是存在大量的乱码。这是由于没有对换行符和制表符的字符编码进行处理导致的。接下来就来增加对换行符和制表符的支持。
制表符和换行符的字符编码如下:
- 0x09: 制表符
- 0x0a: 换行符
制表符是用来对齐字符显示位置的。制表符的功能是在当前位置到下一个制表位之间填充上空格。这里作者将制表位设置在0,4,8……等能被4整除的字符数的位置处。对程序做如下的改写:
else if (strncmp(cmdline, "type ", 5) == 0)
{for (y = 0; y < 11; y++) {s[y] = ' ';}y = 0;for (x = 5; y < 11 && cmdline[x] != 0; x++) {if (cmdline[x] == '.' && y <= 8) {y = 8;} else {s[y] = cmdline[x];if ('a' <= s[y] && s[y] <= 'z') {s[y] -= 0x20;} y++;}}for (x = 0; x < 224; ) {if (finfo[x].name[0] == 0x00) {break;}if ((finfo[x].type & 0x18) == 0) {for (y = 0; y < 11; y++) {if (finfo[x].name[y] != s[y]) {goto type_next_file;}}break; }
type_next_file:x++;}if (x < 224 && finfo[x].name[0] != 0x00) {/* 找到文件的情况 */y = finfo[x].size;p = (char *) (finfo[x].clustno * 512 + 0x003e00 + ADR_DISKIMG);cursor_x = 8;for (x = 0; x < y; x++) {/* 逐字输出 */s[0] = p[x];s[1] = 0;if (s[0] == 0x09) { /* 制表符 */for (;;) {putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, " ", 1);cursor_x += 8;if (cursor_x == 8 + 240) {cursor_x = 8;cursor_y = cons_newline(cursor_y, sheet);}if (((cursor_x - 8) & 0x1f) == 0) {break; /* 能被32整除则break */}}} else if (s[0] == 0x0a) { /* 换行 */cursor_x = 8;cursor_y = cons_newline(cursor_y, sheet);} else if (s[0] == 0x0d) { /* 回车符 *//* 这里暂不进行任何操作 */} else { /* 一般字符 */putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, s, 1);cursor_x += 8;if (cursor_x == 8 + 240) {cursor_x = 8;cursor_y = cons_newline(cursor_y, sheet);}}}
这里把strcmp换成了strncmp,只比较前5个字符,这样就不需要逐个比较了。
(cursor_x - 8) & 0x1f这一句,用横坐标减8,是去除边框的8个像素;而&0x1f用来判断能否整除32。每个制表符相隔4个字符,每个字符是8个像素,也就是制表符之间相隔32个像素,通过这种方式来判断制表符的位置。
从显示的内容看,仍然有些乱码。不过这是注释的日语部分,这里就暂时忽略了。
2. FAT支持
上面的type命令,其实只能展示512字节以内的文件。对于512字节以上的文件,显示可能会有问题。
这里涉及到Windows的磁盘管理。存放512字节以上的文件时,有时并不存入连续的扇区中,这样我们需要找到文件的下一个扇区。而这一信息,磁盘中是有记录的,找到这一记录就可以正确读取文件内容了。这个记录存放的位置为0柱面,0磁头,2扇区开始的9个扇区,在磁盘映像中的地址为0x000200-0x0013ff。这个记录被称为FAT(file allocation table)。
……file_readfat(fat, (unsigned char *) (ADR_DISKIMG + 0x000200));……
if (x < 224 && finfo[x].name[0] != 0x00)
{p = (char *) memman_alloc_4k(memman, finfo[x].size);file_loadfile(finfo[x].clustno, finfo[x].size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));cursor_x = 8;for (y = 0; y < finfo[x].size; y++) {s[0] = p[y];s[1] = 0;if (s[0] == 0x09) { for (;;) {putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, " ", 1);cursor_x += 8;if (cursor_x == 8 + 240) {cursor_x = 8;cursor_y = cons_newline(cursor_y, sheet);}if (((cursor_x - 8) & 0x1f) == 0) {break; }}} else if (s[0] == 0x0a) { cursor_x = 8;cursor_y = cons_newline(cursor_y, sheet);} else if (s[0] == 0x0d) { } else { putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, s, 1);cursor_x += 8;if (cursor_x == 8 + 240) {cursor_x = 8;cursor_y = cons_newline(cursor_y, sheet);}}}memman_free_4k(memman, (int) p, finfo[x].size);} else {putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, "File not found.", 15);cursor_y = cons_newline(cursor_y, sheet);}
由于磁盘映像中的FAT使用了微软公司的算法进行了压缩,首先需要解压,file_readfat函数即是实现解压的功能:
void file_readfat(int *fat, unsigned char *img)
{int i, j = 0;for (i = 0; i < 2880; i += 2) {fat[i + 0] = (img[j + 0] | img[j + 1] << 8) & 0xfff;fat[i + 1] = (img[j + 1] >> 4 | img[j + 2] << 4) & 0xfff;j += 3;}return;
}
这里就是将算法转换为程序代码实现。在fat数组中,存放着文件下一个扇区的记录。当文件大小在512字节以上时,存放在多个扇区中。我们从fat数组的第一条记录中获取文件第一个扇区的位置,将这个扇区的内容读取出来;然后再从fat数组的第二条记录中获取文件第二个扇区的位置,再将这个扇区的内容读取出来;……以此类推,每读完一个扇区就去fat数组中查询下一条记录,直到记录中的值为FFF,这表示文件已经读完了。
接下来就通过file_loadfile函数将文件读取出来。从代码中可以看出,文件大小在512字节以上,则根据fat获取下一个扇区的位置,持续读取,直到剩余的内容在512字节以内,直接读出。
void file_loadfile(int clustno, int size, char *buf, int *fat, char *img)
{int i;for (;;) {if (size <= 512) {for (i = 0; i < size; i++) {buf[i] = img[clustno * 512 + i];}break;}for (i = 0; i < 512; i++) {buf[i] = img[clustno * 512 + i];}size -= 512;buf += 512;clustno = fat[clustno];}return;
}
3. 应用程序运行
到目前为止我们已经完成了读取文件的功能。应用程序也是一个文件,如何将其运行起来呢?
需要为应用程序创建一个内存段。将应用程序读入进来,跳转到该段中的程序,就可以开始运行了。跳转的方法,在之前多任务的部分已经讲过,是farjump指令。
……else if (strcmp(cmdline, "hlt") == 0)
{/* 启动hlt.hrb应用程序 */for (y = 0; y < 11; y++) {s[y] = ' ';}s[0] = 'H';s[1] = 'L';s[2] = 'T';s[8] = 'H';s[9] = 'R';s[10] = 'B';for (x = 0; x < 224; ) {if (finfo[x].name[0] == 0x00) {break;}if ((finfo[x].type & 0x18) == 0) {for (y = 0; y < 11; y++) {if (finfo[x].name[y] != s[y]) {goto hlt_next_file;}}break; /* 找到文件 */}
hlt_next_file:x++;}if (x < 224 && finfo[x].name[0] != 0x00) {/* 找到文件的情况 */p = (char *) memman_alloc_4k(memman, finfo[x].size);file_loadfile(finfo[x].clustno, finfo[x].size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));set_segmdesc(gdt + 1003, finfo[x].size - 1, (int) p, AR_CODE32_ER);farjmp(0, 1003 * 8);memman_free_4k(memman, (int) p, finfo[x].size);} else {putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, "File not found.", 15);cursor_y = cons_newline(cursor_y, sheet);}cursor_y = cons_newline(cursor_y, sheet);
}
……
这里我们运行了一个hlt.hrb的应用程序。为了不与windows混淆,这里作者自定义了扩展名.hrb。
我们首先根据文件的大小,通过memman_alloc_4k函数分配一段内存,首地址保存在变量p中。然后调用file_loadfile将hlt.hrb文件读入到这段内存中。接下来,我们设置一个地址段,段号设置为1003(因为1-2号分配给了dsctbl.c使用,3-1002号分配给了前面的多任务mtask.c使用,因此这里使用1003号,当然使用之后的段号也是没问题的),段的首地址设置为刚才p变量中的地址。这样一切准备就绪,我们使用farjmp指令跳转到1003号段,程序就可以开始运行了。
这个hlt.hrb应用程序其实执行的就是HLT指令,因此执行后CPU进入睡眠状态,现象其实就是命令行窗口完全没有反应了。不过还是可以切换到任务A,或者通过鼠标移动任务A的窗口。
下一篇中将会实现应用程序对操作系统功能的调用。敬请期待。