Day19

今天的主要任务是寻找文件/程序存储在内存中的位置,从而加载它们。

一些感想和问题

读取文件

先从简单的开始讲起。回到昨天的结构体

struct FILEINFO
{
    unsigned char name[8], ext[3], type;
    char reserve[10];
    unsigned short time, date, clustno;
    unsigned int size;
};

clustno,代表从哪个扇区(512B)开始存放,同时会有一个基址,通过基址+扇区号*512,就能得到磁盘映像的其实地址了。
但是文件有可能并不存放在连续的扇区中(磁盘碎片的缘故),每个文件分配的扇区,会记录在FAT(file allocation table)中,查询FAT即可。FAT存放在0x00200~0x0013ff中(FAT会有备份,第二份用来保证数据没有出错)。
不过FAT有特殊的编码方式,所以需要解码。解码方式如下:
首先把数据以每3个字节分为一组,并进行换位

F0 FF FF -> FF0 FFF
12 34 56 -> 412 563

像这样,03 40 00 就会相应变成

03 40 00 -> 003 004
12 34 56 -> 412 563

之后对FAT解码,可以得到
FAT
因为clustno初始号为2,所以就找FAT对应的2号位置,发现文件的下一部分储存在3号扇区,就这么一直读下去,直到遇到FF8~FFF为止。把这个理解为一个链表,就好理解多了。

执行程序

每个程序都会有一个内存段,没理解错的话就是各自都有各自的segment,执行程序的话,先给其注册一个gdt,之后farjump到对应的内存段即可。

重新加载FAT?

在16c中,readfat是在console一开始就执行的,不过目前还不涉及文件的改动,但是应该每次需要读文件的时候,再去取一下FAT的内容吧。

执行应用程序

这里的应用程序,更多意义上还是像是console提供的一些函数,只不过存放的时候保存成了文件而已。
如果说真正的应用程序,像这样一条一条地注册命令(指strcmp)感觉上就不对,应该像找文件那样,看有没有合适的应用程序执行吧。
不过话说回来,现在也没有文件管理系统呢,这么想也有点操之过急了。

看来文件在磁盘中的存放方式也不简单,花了两天来讲这个东西。

type命令

harib16a

type命令,就是输出文件的全部内容。首先在文件结构体里找有没有对应的文件,找到之后找文件的扇区,这样从扇区里开始读出数据就行了。
虽然看着很长,其实不太难理解,过程就是准备文件名、找文件、输出/报错

if(strncmp(cmdline, "type ", 5) == 0) // 直接用strncmp好了。。
{
    // 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; // 与其说是type,应该说是find比较合适
                }
            }
            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);
}

这是初步的输出文件的方法。

type命令改良

harib16b

增加了对制表符(‘\t’)、换行符(‘\n’)、回车符的支持(‘\r’)。
以前也有所耳闻windows和linux下存储文件每行结尾的区别。不过是历史遗留问题罢了。现在的话遇到’\r’忽略就好了。
改写一下上一节逐字输出的部分:

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);
        }
    }
}

制表部分,就是每满足8*4个像素就是4个字符的位置了,其它也没什么好说的。

对FAT的支持

harib16c

学习了FAT的知识点之后,我们要做的就是把通过FAT读出文件,最好放到一个缓冲区中,然后直接从缓冲区输出文件。
首先,准备好读取FAT的函数:

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;
}

有了FAT,当一个type命令来的时候,把整个文件加载到内存中

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];
    }
}

在找文件之前,先要加载FAT(目前是一开始就加载了。。存疑)

int *fat = (int *)memman_alloc_4k(memman, 4 * 2880);
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;
        ...
    }
    memman_free_4k(memman, (int)p, finfo[x].size);
}

这样不连续的文件存储也做好了

代码整理

harib16d

早该把console什么的分出去了。。

第一个应用程序

harib16e

既然能读取文件,那么也能对应执行“文件”
首先看之前的一个汇编

[BITS 32]
    CLI
fin:
    HLT
    JMP fin

把它汇编之后生成hrb格式的可执行文件,一同载入内存。
之后执行命令其实和之前的类似

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; // 其实也是find的意思
                }
            }
            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);
}

和之前type来寻找其实也差不多。。
不过还是强调一下吧,要读入内存、注册GDT、farjump。