《30天自制操作系统》19_day_学习笔记
harib16a:
这一部分,我们在系统中实现读取文件内容的命令type。在windows中,输入“type 文件名”,在Linux中,输入“cat 文件名”都可以显示文件的内容。我们先来看看如何读取文件本身的内容。这一节的前面部分,笔者花了大量篇幅去寻找文件内容在内存中的位置得到了以下规律:
1、clustno 表示文件从哪个扇区开始放:
- HARIBOTE.SYS 02 00 -> clustno = 0x0002 :0x004200
- IPL10.NAS 39 00 -> clustno = 0x0039 :0x00b000
- MAKE.BAT 3F 00 -> clustno = 0x003F :0x00bc00
2、磁盘映像中的地址 = clustno * 512 +0x003e00
好了接下来我们修改CMD任务中的内容
void console_task(struct SHEET *sheet, unsigned int memtotal) { //..... char s[30], cmdline[30], *p; //..... for (;;) { //..... } 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++) {//从cmdline[30]的第5个字符开始,将文件名写到数组s中 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; ) { //在224个文件段信息里面找 if (finfo[x].name[0] == 0x00) { //第一字节为0x00,表示这一段不包含文件信息 break; } if ((finfo[x].type & 0x18) == 0) {//包含信息 for (y = 0; y < 11; y++) { //该段的文件名信息和S数组比较 if (finfo[x].name[y] != s[y]) { goto type_next_file; //不想等,不是这个文件, } } break; /* 找不到文件 */ } type_next_file: //继续往下找 x++; } if (x < 224 && finfo[x].name[0] != 0x00) { /* 找到文件的情况,x为文件的段号 */ y = finfo[x].size; //文件的大小 //文件内容在内存中的位置 p = (char *) (finfo[x].clustno * 512 + 0x003e00 + ADR_DISKIMG); cursor_x = 8; //用来记录是否到达行尾,行首初始化为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 { /* 没有找到文件的情况,输出提示信息File not found.换行 */ 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); } //..... } }
harib16b:
我们发现系统中的type命令无法对制表符、换行、回车进行相应的操作和处理;笔者首先给出了这三个特殊字符的字符编码,接着Windows和Linux中的换行符进行了比较,最后作者决定用以下编码来表示这三个特殊字符:
- 0x09......制表符:这里设定4个空格的长度为一个制表符
- 0x0a......换行符:换行
- 0x0d......回车符:忽略
按照上述字符编码,修改CMD任务:
void console_task(struct SHEET *sheet, unsigned int memtotal) { //..... for (;;) { //..... } else if (strncmp(cmdline, "type ", 5) == 0) { //只比较前面的5个字符 //...... 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) {//CMD窗口宽度为8个像素笔者规定的一个制表符是4个字符,因此是32个像素的值 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); } } } } //..... } }
harib16c:
type命令有时无法正确显示文件的内容。而且如果文件大小超过了512字节,也会出现问题。怎么存储大于512字节的内容呢?我们来看看FAT和clustno.
FAT(file allocation table):文件分配表,用来记录文件在磁盘中存放位置的表。它位于从0柱面、0磁头、2扇区开始的9个扇区中,在磁盘映像中相当于0x000200-0x0013ff。微软已经对这个FAT进行了压缩,我们将数据以3个字节为一组进行解压缩:规则如下:ab ce ef ->dab efc;微软将FAT看作是很重要的磁盘信息,为此在磁盘中放了两份FAT:0x000200-0x0013ff和0x001400-0x0025ff。
clustno:表示文件从哪个扇区开始放
- HARIBOTE.SYS 02 00 -> clustno = 0x0002 :0x004200
- IPL10.NAS 39 00 -> clustno = 0x0039 :0x00b000
- MAKE.BAT 3F 00 -> clustno = 0x003F :0x00bc00
以HARIBOTE.SYS为例,读取文件内容顺序如下
1、clustno = 0x0002;读取从clustno * 512 +0x003e00开始的512字节的内容
2、clustno = FAT(clustno) = 3;读取从clustno * 512 +0x003e00开始的512字节的内容
3、clustno = FAT(clustno) = 4;读取从clustno * 512 +0x003e00开始的512字节的内容
4、依次类推,直到clustno = FAT(clustno) = (FF8-FFF)表示文件尾部
我们根据上面的原理和信息来写程序
void console_task(struct SHEET *sheet, unsigned int memtotal) { //...... int *fat = (int *) memman_alloc_4k(memman, 4 * 2880); //将FAT信息解压缩到fat数组中。。。 file_readfat(fat, (unsigned char *) (ADR_DISKIMG + 0x000200)); for (;;) { //....... else if (strncmp(cmdline, "type ", 5) == 0) { //type命令 //...... if (x < 224 && finfo[x].name[0] != 0x00) { //第x段有文件信息 p = (char *) memman_alloc_4k(memman, finfo[x].size);//分配一块和文件大小相同的内存p //file_loadfile将文件中的内容都入到上面分配的内存中 file_loadfile(finfo[x].clustno, finfo[x].size, p, fat, (char *) (ADR_DISKIMG + 0x003e00)); cursor_x = 8; //接下来和以前的一样,只不过这里要显示的文件内容都在数组P中 for (y = 0; y < finfo[x].size; y++) { //遍历文件(大小为finfo[x].size) s[0] = p[y]; s[1] = 0; //..... //这里处理特殊字符,制表符,换行,回车(看一部分)! } memman_free_4k(memman, (int) p, finfo[x].size);//用完了之后把分配的内存p释放掉 } //...... } } void file_readfat(int *fat, unsigned char *img){ //将FAT信息解压缩到fat中|规则如下:ab ce ef ->dab efc 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; } void file_loadfile(int clustno, int size, char *buf, int *fat, char *img) { //将文件的内容都写到分配的内存中buf 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; }
harib16d:
我们到这里发现代码量已经比较大了,读起来比较吃力,下面我们来对代码进行一下整理:
-
- 窗口相关函数->winsows.c
- 命令行窗口相关函数->console.c
- 文件相关的函数->
整理后,工程代码变得清晰了很多,虽然内容没有什么变化。
harib16e:
前面我们已经能够读取文件的内容了,下面我们要尝试着运行应用程序。我们从下面最简单的开始,将下面四行汇编代码保存为hlt.nas,接着用nask进行汇编,生成hlt.hrb(就是haribote的缩写,这里我们用了自己定义的扩展名.hrb)。之后将hlt.hrb写到镜像中,再用前面编写的file_loadfile将hlt.hrb的内容放到一个大小相等的临时变量中p中(相当于一个缓冲区),接着把这个临时变量p加载到GDT的某个段中,最后调用farjmp()执行GDT中这个段中的程序hlt.hrb。怎么样,原理是不是很容易,下面我们看看代码具体怎么做的。
;hlt.nas 4行的汇编代码,我们将要汇编的源程序 [BITS 32] fin: HLT JMP fin
void console_task(struct SHEET *sheet, unsigned int memtotal) { //..... for(;;) { //.... else if (strcmp(cmdline, "hlt") == 0) { /* 接收到了hlt字符串 */ for (y = 0; y < 11; y++) { s[y] = ' ';//s初始化 } 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; //找到第一个没有文件信息的片段(片段号退出时为X) } if ((finfo[x].type & 0x18) == 0) { //有文件信息的片段 for (y = 0; y < 11; y++) { if (finfo[x].name[y] != s[y]) {//文件不是hlt.hrb文件 goto hlt_next_file; //跳转 } } break; /* 文件是hlt.hrb文件,BREAK */ } hlt_next_file://这里继续循环,找下一个文件是不是hlt.hrb x++; } if (x < 224 && finfo[x].name[0] != 0x00) { /* 找到了hlt.hrb */ p = (char *) memman_alloc_4k(memman, finfo[x].size);//分配一块和文件hlt.hrb大小相同的内存p //把这块内存写到GDT的1003号中(这里一般是空闲的,可以写东西 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); //任务切换,这里会加载TR寄存器到GDT的1003号,执行 memman_free_4k(memman, (int) p, finfo[x].size); //执行完成后,释放掉内存p } else { /* 没有找打文件hlt.hrb,输出提升信息 "File not found."*/ 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); } //..... } }