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解码,可以得到
因为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。