本篇内容涉及到文件与文件系统,以及应用程序的运行。首先实现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的窗口。
下一篇中将会实现应用程序对操作系统功能的调用。敬请期待。