第19天 应用程序

第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.SYS02 000x002
    IPL10.NAS39 000x0039
    MAKE.BAT3F 000x003f
  • 从磁盘映像文件haribote.img中找到HARIBOTE.SYS、IPL10.NAS和MAKE.BAT文件开始的位置:

    • HARIBOTE.SYS: (在大概0x004200位置,根据第3天可知)
    • IPL10.NAS:(在大概0x00b000附近)
    • MAKE.BAT:(在大概0x00bc00附近)
  • clustno和文件实际位置的对应关系:

    文件clustno实际位置
    HARIBOTE.SYS0x00020x4200
    IPL10.NAS0x00390xb000
    MAKE.BAT0x003f0xbc00
  • 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整除。
  • make后用VMware运行:

    • 对比harib16a中的type ipl10.nas,已经可以看出明显的对制表符和换行的支持了。
    • 显示的乱码是中文注释,因为现在还不支持中文。

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都没有反应呢。

6. 写在昨天

  • 现在是2020.4.23 10:25. 这篇文档应该昨天写完的,但是却拖到了现在才写完,真是赧然啊。
  • 昨天的确有点累,连续的开发,让我都快敲不动键盘了,因此,昨日中午12点以后,我便睡下了,睡了近3个小时,醒来后浑身不舒服。
  • 然后,到昨晚23:00左右,我已经完成了harib16d的文档编写。实在写不动了,就没再写了。
  • 还有,前天,也就是4.21,文档结束的比较早,我上网解决了Github上图片无法访问的难题,原来是DNS的问题!!可恶!
  • 快到400页了哦,明天就是第20天了,十位数字终于是2了,这就意味着,完成在即(我是不是有点太乐观了(笑))!
  • 那就继续吧!
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值