第19天 应用程序
2020.4.21
1. type命令(harib16a)
-
type命令,显示文件内容。liunx中对应的命令是cat。
-
要想实现type命令,就必须读取文件内容。
-
回想一下昨天的内容:用二进制查看器查看了磁盘映像的内容,并定义了一个结构体用于存放32字节的数据。
struct FILEINFO { unsigned char name[8], ext[3], type; char reserve[10]; unsigned short time, date, clustno; unsigned int size; };
-
其中clustno这个2字节变量,代表文件从磁盘上的那个扇区开始存放。根据磁盘映像文件
将clustno成员读出来:文件 读取数据 clustno HARIBOTE.SYS 02 00 0x002 IPL10.NAS 39 00 0x0039 MAKE.BAT 3F 00 0x003f -
从磁盘映像文件haribote.img中找到HARIBOTE.SYS、IPL10.NAS和MAKE.BAT文件开始的位置:
- HARIBOTE.SYS: (在大概0x004200位置,根据第3天可知)
- IPL10.NAS:(在大概0x00b000附近)
- MAKE.BAT:(在大概0x00bc00附近)
- HARIBOTE.SYS: (在大概0x004200位置,根据第3天可知)
-
clustno和文件实际位置的对应关系:
文件 clustno 实际位置 HARIBOTE.SYS 0x0002 0x4200 IPL10.NAS 0x0039 0xb000 MAKE.BAT 0x003f 0xbc00 -
0x003f-0x0039 = 6; 0xbc00-0xb000 = 0xc00。也就是说clustno每增加6,磁盘映像文件就增加0xc00个字节。也就是说clustno每增加1,磁盘映像文件就增加0xc00/6字节=512字节=一扇区!
-
当clustno=0x0002时,磁盘映像的位置是0x4200,当clustno=0x0000时,那么磁盘映像的位置就是0x3e00。
-
因此,便有如下公式:
- 磁盘映像中的地址 = clustno * 512 + 0x3e00
- 经演算,这个公式正确。
- 有了这个公式,只需要将文件的内容逐字节读出来即可。
-
修改console_task:
void console_task(struct SHEET *sheet, unsigned int memtotal) { …… for (;;) { io_cli(); if (fifo32_status(&task->fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 键盘数据 */ if (i == 8 + 256) { /*退格键*/ …… } else if (i == 10 + 256) { /* Enter */ …… if (strcmp(cmdline, "mem") == 0) { /* mem命令 */ …… } else if (strcmp(cmdline, "cls") == 0) { /* cls命令 */ …… } else if (strcmp(cmdline, "dir") == 0) { /* dir命令 */ …… } else if (cmdline[0] == 't' && cmdline[1] == 'y' && cmdline[2] == 'p' && cmdline[3] == 'e' && cmdline[4] == ' ') { /* type命令 */ /* 准备文件名,占11位 */ for (y = 0; y < 11; y++) { s[y] = ' '; } y = 0; for (x = 5; y < 11 && cmdline[x] != 0; x++) { if (cmdline[x] == '.' && y <= 8) { /*不足8,空格填充,置为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) { /* 错误指令 */ …… } …… } else { …… } } …… } } }
- 由于strcmp函数只能匹配整个字符串是否相等,因此这里判断是不是type指令时,用的是按字符匹配。
-
make
后用VMware运行:- 在命令行窗口输入
type make.bat
- 在命令行窗口输入
type ipl10.nas
会显示乱七八糟的字符,因为没有对文件中的换行和制表符的字符编码进行处理。
- 在命令行窗口输入
type haribote.sys
会显示乱码。因为haribote.sys是机器语言文件。
- (乱码显示不明显,运行时可看出)
- 在命令行窗口输入
2. type命令改良(harib16b)
-
实现对换行的处理,需要支持的特殊字符编码如下:
- 0x09:制表符:显示空格直到x被4整除。
- 0x0a:换行符:换行。
- 0x0d:回车符:忽略。
-
制表符的功能是:在当前位置到下一个制表位之间填充空格。规定:制表位是0、4、8……这样的4的倍数的位置。
- 这里的制表符是水平制表符
- 垂直制表符不会被使用
-
linux中换行是0x0a,windows中换行是0x0d 0x0a。
-
换行:光标垂直下移一行;回车,光标回到当前行的行首。
-
这里,规定:0x0a是换行加回车,0x0d忽略。
-
修改console_task中的有关type命令部分:
void console_task(struct SHEET *sheet, unsigned int memtotal) { …… for (;;) { io_cli(); if (fifo32_status(&task->fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 键盘数据 */ if (i == 8 + 256) { /* 退格键 */ …… } else if (i == 10 + 256) { /* Enter */ …… if (strcmp(cmdline, "mem") == 0) { /* mem命令 */ …… } else if (strcmp(cmdline, "cls") == 0) { /* cls命令 */ …… } else if (strcmp(cmdline, "dir") == 0) { /* dir命令 */ …… } else if (strncmp(cmdline, "type ", 5) == 0) { /* type命令 */ …… if (x < 224 && finfo[x].name[0] != 0x00) { /* 找到文件的情况 */ …… 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); } } } } else { /*没有找到文件的情况*/ …… } cursor_y = cons_newline(cursor_y, sheet); } else if (cmdline[0] != 0) { /* 错误值指令 */ …… } …… } else { …… } } …… } } }
- 对于type指令的判断不再使用单个字符判断,而是使用新函数
strncmp
- 函数声明:
int strncmp(const char *str1, const char *str2, size_t n)
- 参数:
- str1-- 要进行比较的第一个字符串。
- str2-- 要进行比较的第二个字符串。
- n-- 要比较的最大字符数。
- 返回值:
- 如果返回值 < 0,则表示 str1 小于 str2。
- 如果返回值 > 0,则表示 str2 小于 str1。
- 如果返回值 = 0,则表示 str1 等于 str2。
- 函数声明:
- 对于制表符的处理:
if (((cursor_x - 8) & 0x1f) == 0) { break; }
- cursor_x的初始值是16,因为前面有边框占的8个像素以及提示符
>
占的8个像素。 - cursor_x-8:制表符算上提示符
>
,边框不算是占用制表符的位置。 - 只有cursor_x-8的后5位都是0的时候,
((cursor_x - 8) & 0x1f)
才能是0,那么cursor_x-8一定是32的倍数。cursor_x-8是8的倍数,制表符以4位单位,所以要被32整除。
- cursor_x的初始值是16,因为前面有边框占的8个像素以及提示符
- 对于type指令的判断不再使用单个字符判断,而是使用新函数
-
make
后用VMware运行:
- 对比harib16a中的
type ipl10.nas
,已经可以看出明显的对制表符和换行的支持了。 - 显示的乱码是中文注释,因为现在还不支持中文。
- 对比harib16a中的
3. 对FAT的支持(harib16c)
-
harib16a/b中的type命令是有问题的:
- 有时候会无法正确显示文件内容。
- 但肯定能够正确显示文件开头的512字节内容。
-
按照Windows管理磁盘的方法,保存大于512字节的文件时,有时候并不是存入连续的扇区中。 (尽管这种情况很少,还是得解决)
-
对于文件的下一段存放在哪里,在磁盘上是有记录的。这个记录位于从0柱面、0磁头、2扇区开始的9个扇区中,在磁盘映像中相当于
0x000200~0x0013ff
(扇区从1开始。) -
这个记录就叫做FAT(file allocation table,文件分配表),记录文件在磁盘中存放位置的表。
-
haribote.img中FAT:
- 这个FAT我们暂时无法看懂,这是微软公司设计了算法进行压缩后的FAT。因此需要解压缩。
-
解压方式:
-
解压方式详解:
-
解压后信息分析:
- HARIBOTE.SYS的clustno=2;找解压缩后的FAT,得到FAT[2]=0x003=3;FAT[3]=0x004=4;……直到FAT[57]=0xfff。0xfff代表文件结尾。也就是说HSRIBOTE.SYS文件结束了。这样,只要知道文件的起始clustno,那么就可以根据FAT[]数组序号和数组元素的对应关系,知道文件存放的扇区。
- 只要有一个环节出错,后面的都会混乱。
- 微软公司将FAT看作是重要的磁盘信息,为此在磁盘中存放了2份FAT。第1份FAT位于0x0002000x0013ff,第2份位于0x0014000x0025ff。其中第二份是备份FAT,和第一份完全相同。
-
修改console_task:
void console_task(struct SHEET *sheet, unsigned int memtotal) { …… for (;;) { io_cli(); if (fifo32_status(&task->fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 键盘数据 */ if (i == 8 + 256) { /* 退格键 */ …… } else if (i == 10 + 256) { /* Enter */ …… if (strcmp(cmdline, "mem") == 0) { /* mem指令 */ …… } else if (strcmp(cmdline, "cls") == 0) { /* cls指令 */ …… } else if (strcmp(cmdline, "dir") == 0) { /* dir指令 */ …… } else if (strncmp(cmdline, "type ", 5) == 0) { /* type指令 */ …… 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++) { /* 逐字节输出 */ …… } memman_free_4k(memman, (int) p, finfo[x].size); /*释放临时内存*/ } else { /* 没有找到文件 */ …… } cursor_y = cons_newline(cursor_y, sheet); } else if (cmdline[0] != 0) { /* 没有找到文件 */ …… } …… } else { …… } } …… } } }
- 磁盘有2880个扇区,fat[]序号得需要2880,即FAT数组的大小至少2880。
- 编写file_readfat函数:
void file_readfat(int *fat, unsigned char *img) /* 将磁盘映像中的FAT解压缩 */ { 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; }
- 编写file_loadfile函数:
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; }
- 先用函数file_readfat将压缩状态的FAT解压到fat[];然后,在type命令中,申请一块和文件大小的内存空间,用file_loadfile将文件读入内存。这样内存中的文件内容就已经排列成了正确的顺序,用之前的程序来显示文件内容即可。显示完成后,释放用于临时存放文件内容的内存空间。
-
make
后用VMware顺利运行,输入type命令显示正常。 -
关于FAT压缩:
4. 代码整理(harib16d)
- harib16c中的bootpack.c代码行数已经到达648行。
- 整理一下源代码:
- 窗口相关函数–>window.c
- 命令行窗口相关函数–>console.c
- 文件相关函数–>file.c
- bootpack.h中的定义:
/* window.c */ void make_window8(unsigned char *buf, int xsize, int ysize, char *title, char act); void putfonts8_asc_sht(struct SHEET *sht, int x, int y, int c, int b, char *s, int l); void make_textbox8(struct SHEET *sht, int x0, int y0, int sx, int sy, int c); void make_wtitle8(unsigned char *buf, int xsize, char *title, char act); /* console.c */ void console_task(struct SHEET *sheet, unsigned int memtotal); int cons_newline(int cursor_y, struct SHEET *sheet); /* file.c */ struct FILEINFO { unsigned char name[8], ext[3], type; char reserve[10]; unsigned short time, date, clustno; unsigned int size; }; void file_readfat(int *fat, unsigned char *img); void file_loadfile(int clustno, int size, char *buf, int *fat, char *img);
make
后用VMware顺利运行。
5. 第一个应用程序(harib16e)
-
能够读取文件信息,运行应用程序也就不难实现了。
-
编写3个字节的应用程序hlt:
[BITS 32] fin: HLT JMP fin
- 将上述代码保存成hlt.nas,然后使用nask编译,生成hlt.hrb。拓展名hrb是haribote的缩写。如果使用.exe的话会Windows中的可执行文件冲突,因此使用了自定义的拓展名。
- 当然,要把hlt.hrb保存到磁盘映像中,还需要修改Makefile。
-
运行文件内容的方式:
- 首先,将文件读入到内存(可以使用type调用过的函数file_loadfile)。
- 其次,应用程序不知道自己被读入到哪个内存地址,暂时用
ORG 0
来生成。为了应用程序能够顺利运行,需要为其创建一个内存段。 - 段创建完成以后,只需要goto到该段的程序中,程序就会运行了。即调用farjmp函数实现任务切换。
- 这样,我们不需要改动naskfunc.nas就可以运行hlt.hrb了。
-
修改console_task(console.c中)
void console_task(struct SHEET *sheet, unsigned int memtotal) { …… struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT; /*获取gdt开始地址*/ …… for (;;) { io_cli(); if (fifo32_status(&task->fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 键盘数据 */ if (i == 8 + 256) { /* 退格键 */ …… } else if (i == 10 + 256) { /* Enter */ …… if (strcmp(cmdline, "mem") == 0) { /* mem命令 */ …… } else if (strcmp(cmdline, "cls") == 0) { /* cls命令 */ …… } else if (strcmp(cmdline, "dir") == 0) { /* dir指令 */ …… } else if (strncmp(cmdline, "type ", 5) == 0) { /* type指令 */ …… } 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); /*开辟内存段1003号*/ farjmp(0, 1003 * 8); /*任务跳转*/ memman_free_4k(memman, (int) p, finfo[x].size); /*释放内存空间,hlt返回不了,所以在这里这句不会执行*/ } 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) { …… } …… } else { …… } } …… } } }
- hlt指令是用来启动hlt.hrb这个应用程序的。
- hlt.hrb成功写入内存后,将其注册成GDT的1003号。12号右dsctbl.c使用,31002号由mtask.c使用(最多1000个任务)。因此,暂时让这个应用程序使用GDT的1003号。
- 当内存段创建完成后,用farjmp跳转并运行。
-
make
后用VMware运行:- 输入指令hlt启动应用程序hlt.hrb:
- console窗口完全没有了反应。使用Tab键仍能切换回task_a。
- 将hlt.nas改成如下,以确定命令行窗口的停止到底是因为运行了hlt.hrb还是出现了BUG。
[BITS 32] CLI fin: HLT JMP fin
- 在开头加上了一个CLI指令,这样这个程序就变成了4字节。把中断禁止掉,按Tab键也无法切换回task_a,鼠标也不会动了。
- VMware显示提示,显然是CLI起作用了。关掉提示,果然无论如何操作OS都没有反应呢。
- 在开头加上了一个CLI指令,这样这个程序就变成了4字节。把中断禁止掉,按Tab键也无法切换回task_a,鼠标也不会动了。
- 输入指令hlt启动应用程序hlt.hrb:
6. 写在昨天
- 现在是2020.4.23 10:25. 这篇文档应该昨天写完的,但是却拖到了现在才写完,真是赧然啊。
- 昨天的确有点累,连续的开发,让我都快敲不动键盘了,因此,昨日中午12点以后,我便睡下了,睡了近3个小时,醒来后浑身不舒服。
- 然后,到昨晚23:00左右,我已经完成了harib16d的文档编写。实在写不动了,就没再写了。
- 还有,前天,也就是4.21,文档结束的比较早,我上网解决了Github上图片无法访问的难题,原来是DNS的问题!!可恶!
- 快到400页了哦,明天就是第20天了,十位数字终于是2了,这就意味着,完成在即(我是不是有点太乐观了(笑))!
- 那就继续吧!