????????本章主要介绍了文件处理的相关操作以及尝试制作第一个应用程序hlt。
一 type命令
????????与Linux里面的type命令不同,Windows命令行中的type命令是用来查看文件内容的。在自制的操作系统中,我们模仿的是Windows的type功能。
? ? ? ? 怎么才能把文件内容读到命令行窗口中呢?首先要找到它存放的扇区。这个在上一章【操作系统】30天自制操作系统--(17)命令行窗口2中有所描述,关注clustno这个变量,存放的就是文件的存储扇区:
struct FILEINFO {
unsigned char name[8], ext[3], type; //1.文件名 2.扩展名 3.文件类型
char reserve[10]; //4.保留
unsigned short time, data, clustno; //5.存放时间 6.存放日期 7.存放扇区
unsigned int size; //8.文件大小
};
????????将 clustno 的值与在磁盘映像中找到他们的位置对应起来:
? ? ? ? ?可以发现,IPL10.NAS与MAKE.BAT相差 6 个扇区,实际位置相差0xc00字节,正好对应0xc00 / 6 = 0x200 = 512字节,也就是一个扇区的容量。那么可以倒推得到扇区0的位置是0x3e00,扇区 clustno 对应的位置 =?clustno * 512 + 0x3e00。由此,我们只要将文件的内容逐字节读出来并且显示在屏幕上就可以了:
char *p;
// ...
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++) {
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;
}
}
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++) {
// print word by word
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);
}
?????????完成上面的改动,能够查找文件,并从对应内存区间将文件内容拎出来。但是如果文件内容含有一些奇奇怪怪的内容(制表符、换行符、回车符、汉字等),就需要额外的特殊处理:
?
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) {
// 以32分割
break;
}
}
} else if (s[0] == 0x0a) { //换行符处理
cursor_x = 8;
cursor_y = cons_newline(cursor_y, sheet);
} else if (s[0] == 0x0d) { //回车符处理
// do nothing
} 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);
}
}
? ? ? ? 这样便能通过type命令,正确显示大部分的文件内容(尚不支持汉字字符)。
二 支持FAT(file allocation table,文件分配表)
? ? ? ? 上面的处理针对长度小于512字节的文件适用,因为文件是存在一个扇区内的。但是如果遇到大于512字节的文件的话,在windows中可能不是存在连续的扇区中的,这种情况就是磁盘碎片。
? ? ? ? 那么操作系统是怎么管理磁盘碎片的呢?答案是通过位于位于 0x000200~0x0013ff 的FAT(file allocation table,文件分配表),来记录某个文件的下一段存放的扇区信息。
? ? ? ? 对于FAT需要关注两点:
【1】FAT是经过微软的算法压缩过的,解码之后是下面这样:
????????我们对于文件信息部分的 clustno 进行解读: ????????已知clustno = 2,因此我们读取 0x004200~0x0043ff这512个字节。那么接下来应该读取哪里的数据呢? 我们来看FAT的第2号记录,其值为003,也就是说下面的部分存放在 clustno = 3这个位置。按照顺序,我们一直读取到clustno = 57(0x39),参照对应的 FAT 记录,57 号后面是 FFF,代表文件的末尾(一般来说,如果遇到FF8~ FFF的值,就代表文件数据到此结束)。
? ? ? ? ?命令行任务 console_task 中实现代码如下:
// ...
int *fat = (int *) memman_alloc_4k(memman, 4 * 2880);
// 加载 FAT
file_readfat(fat, (unsigned char *) (ADR_DISKIMG + 0x000200));
// ...
else if (strncmp(cmdline, "type ", 5) == 0) {
// 接收输入、转换格式到 s[8]
// 找到文件格式所在的位置 x
if (x < 224 && finfo[x].name[0] != 0x00) {
// 加载文件到 p
p = (char *) (finfo[x].clustno * 512 + 0x003e00 + ADR_DISKIMG);
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;
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; //
}
}
} 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);
}
}
}
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);
}
/* 将磁盘映像中的FAT解压缩 */
void file_readfat(int *fat, unsigned char *img)
{
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中的顺序,从各个扇区加载文件 */
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;
}
【2】对于分段存放的文件,读取文件是一个接力的过程,不能允许其中有损坏,因为一旦中间出现问题,那么后面就都乱了。所以微软将FAT视为最重要的磁盘信息,为此在磁盘中存放了2份。第1份FAT位于0x000200~0x0013ff,第2份位于 0x001400~0x0025ff。其中第2份是备份FAT,内容和第1份完全相同。
三 制作应用程序(HLT)
? ? ? ? 设计一个可以执行HLT功能的应用程序,实现步骤如下:
【1】应用程序的执行首先需要创建应用程序? hlt.nas:
[BITS 32]
fin:
HLT
JMP fin
【2】将上面的文件保存为 hlt.nas,然后用 nask 进行汇编,生成专属可执行文件 hlt.hrb。这个扩展名是自定义的,类似于Windows中的exe。
【3】像type命令一样,我们用file_loadfile将文件的内容读到内存中去。但是应用程序不知道自己被读到了那个内存地址,所以我们需要为其创建一个内存段(这边注册为1003号,因为1~2号由dsctbl.c使用, 而3~1002号由mtask.c使用,所以我们用了1003号)。
【4】段创建好之后,只要goto到该段中的程序(goto相当于汇编里面的farjmp),程序就会开始运行了。
? ? ? ? 代码实现如下:
// ...
struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
// ...
if (strcmp(cmdline, "hlt") == 0) { //hlt命令
// 启动应用程序 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 == 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);
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);
}
? ? ? ? 完成之后,在命令行窗口中执行hlt命令,即可以调用 hlt.hrb 应用程序,成功将程序HLT住。
|