自制操作系统日志——第十九天
今天,我们将制作我们的第一个应用程序。大家来一起加油吧!!
一、type命令
这里的type命令其实就相对于linux的cat命令,作用就是查看文件内容数据。
以下让我们看看磁盘映像里对应的内容,然后我们再根据这个内容把对应的文件内容写出来:
前一天中,我们讲过在文件名信息数据里有一项是代表着该文件在中磁盘的哪一个扇区。由图可知:
haribote.sys ⇒ 02 00 ⇒ clustno = 0x0002
ip10.nas ⇒ 38 00 ⇒ clustno = 0x0038
make.bat ⇒ 3e 00 ⇒ clustno = 0x003E
然后,再查看一下对应的文件位置:
由上图可以看出ipl10 与 make 这两个文件的位置差位:0xBC00 - 0xB000 = 0x0C00 。 而他们的扇区号增加了 0x3e - 0x38 = 6, 最后再用0x0c00 / 6 =0x200,也就512字节!!!
而对应着haribote这个文件,其扇区号为2,存于0x4200处,那么我们假设以从0开始增长(当然扇区一般都是从1开始的, 这里只是便于计算以及后面的编程所需)则当clustno = 0 时,地址就为0x3e00,于是乎我们可以推出如下公式:
磁盘中映像地址= clustno * 512 + 0x003e00 ; 注意进行编程时候,我们需要把存于内存中的初始地址也算上!
为了以防万一,我们还需要把一些文本里的字符也进行判断一下,换行符在Windows中使用的是0x0d0x0a,linux中直接使用0x0a。例如:
0x09 ⇒ 制表符,显示空格直到x被4整除
0x0a ⇒ 换行符,换行
0x0d ⇒ 回车符:忽略
那么接下来,我们就利用这个公式进行编程吧!
void console_task(struct SHEET *sheet, unsigned int memtotal)
{
char s[30], cmdline[30], *p;
略
else if (strncmp(cmdline, "type ", 5) == 0) {
//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 < 244;){
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 < 244 && finfo[x].name[0] != 0x00){
//找到文件
y = finfo[x].size;
p = (char *) (finfo[x].clustno * 512 + 0x003e00 + ADR_DISKIMG);//算出处于内存的32位下的地址
cursor_x = 8;
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) {//减8是因为边框还要8个宽度,每个制表位相隔4个字符
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{
//没找到的情况
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文件格式的整理
再前一节中,我们已经读取了文件的内容数据。不过这有一些小bug在!具体就是当我们的文件内容大于512字节后,其文件内容并不一定是按扇区顺序连续存储的了。。这是由于FAT磁盘存取的特性所决定的,也就是说文件内容的存放并非是连续存储的,而是相当于有一条链将这些扇区按照一定规则进行链接的!!
那么这些链接顺序在哪可以看呢?? 当然就是在磁盘映像的的FAT文件分配表中会存储着这些信息:
当然如果这样子直接看肯定是看不懂的啦,这是因为这里经由微软公司设计的压缩算法进行了压缩了。那么我们来讲解一些怎么进行解压缩:
F0 FD FE ⇒ DF0 FEF
也就是经过这样子的转化就可以进行表示一个扇区的号码了! 那么我制作了一个小窗口用于表示一下解压缩后的内容,大家来一起看看吧:
再解压缩完以后,让我们以haribote.sys这个例子进行详细的解读一下:
- 首先,在前面我们一直clustno = 2 (也就是扇区号),因此我们会读取0x004200~0x0043ff这512字节;
- 接着,我们要到这个FAT表中进行查看 clustno = 2对应的值是多少。
- 再上图中,从右往左依次从0开始递增;因此 2号对应的值就是003,也就是说当我们读取完第二扇区后,查看FAT的2号对应的值就可以直到2号扇区后紧跟着的就是003号扇区。因此,就会跳转到3号扇区继续读;
- 依次类推,如果读完3号扇区,需要继续寻找下一扇区的话,那么就会查看FAT对应的3号的值是几,任何就挑转到哪个扇区里区;
- 一直读FAT对应的值为FF8~FFF中的任何一个时,就代表了文件内容数据的读取完毕!
至此,我们已经了解完了,然后让我们开始编写代码吧:
void console_task(struct SHEET *sheet, unsigned int memtotal)
{
略
int *fat = (int *) memman_alloc_4k(memman, 4 * 2880);
file_readfat(fat, (unsigned char *) (ADR_DISKIMG + 0x000200));
略
for(;;){
if(x < 244 && 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;
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) {//减8是因为边框还要8个宽度,每个制表位相隔4个字符
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);
}
}
}
memman_free_4k(memman, (int) p, finfo[x].size);
然后,新增对文件的读写,首先利用readfileFAT的数据读入,然后再load中,将文件内容拍好正确的顺序进行写入到内存当中:
//文件读取的相关函数
#include "bootpack.h"
//将磁盘映像中的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;
}
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;
}
然后make run一下,发现似乎没啥问题!
好的吧,那么再顺势整理一下文件:
- 窗口相关的函数 ⇒ windows.c
- 命令窗口相关函数 ⇒ console.c
- 文件相关函数 ⇒ file.c
运行第一个程序
以下,我们建立一个代码的程序文件,然后通过nask进行汇编,正常hrb的文件。这里的hrb文件是自定义的拓展名,只是为了和windows的exe文件进行区分以防出现混淆的状况。
然后我们将该程序写入到磁盘映像里,再藉由命令行进行调用:
hrb:
[BITS 32]
fin:
HLT
JMP fin
console.c:
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 < 244 && 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);
}
}
这里我们之所以使用1003 这是因为1~2已经在dsctbl.c中已经用了,而3 ~ 1002号这是在mtask.c中预分配给了多任务了。
然后运行:
这里,当我们输入hlt后就会发现命令行窗口这个任务已经停止运行了!!!嘿嘿嘿,貌似成功了。
总结
今天,我们由进一步的完成了对FAT磁盘文件的读取,以及成功的制造了第一个应用程序,收获满满啊。明天继续加油吧。