【读书笔记-《30天自制操作系统》-18】Day19

本篇内容涉及到文件与文件系统,以及应用程序的运行。首先实现type命令,读取文件并显示;接下来导入对FAT文件系统的支持,实现读取大小512字节以上,存放在不连续扇区中的文件。在此基础上,最终实现读取并运行应用程序。

1. type命令实现

type命令是Windows命令行中用于读取并显示文件内容的命令,对应Linux中的cat命令。为了获取文件信息,这里还是要用到上一篇提到的结构体:

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

其中clustno这个成员表示文件从磁盘上的哪个扇区开始存放。对于当前的操作系统映像,文件的地址有如下公式:

地址 = clustno * 512 + 0x003e00

这样只需将文件的内容读取,并显示出来即可:

c 复制代码
void console_task(struct SHEET *sheet, unsigned int memtotal)
{

	......
	char s[30], cmdline[30], *p;
	......

	for (;;) {
		io_cli();
		if (fifo32_status(&task->fifo) == 0) {
			task_sleep(task);
			io_sti();
		} else {
			......
			if (256 <= i && i <= 511) { 
				if (i == 8 + 256) {
					if (cursor_x > 16) {
						putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, " ", 1);
						cursor_x -= 8;
					}
				} else if (i == 10 + 256) {
					/* Enter */
					putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, " ", 1);
					cmdline[cursor_x / 8 - 2] = 0;
					cursor_y = cons_newline(cursor_y, sheet);
					if (strcmp(cmdline, "mem") == 0) {
						sprintf(s, "total   %dMB", memtotal / (1024 * 1024));
						putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
						cursor_y = cons_newline(cursor_y, sheet);
						sprintf(s, "free %dKB", memman_total(memman) / 1024);
						putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
						cursor_y = cons_newline(cursor_y, sheet);
						cursor_y = cons_newline(cursor_y, sheet);
					} else if (strcmp(cmdline, "cls") == 0) {
						for (y = 28; y < 28 + 128; y++) {
							for (x = 8; x < 8 + 240; x++) {
								sheet->buf[x + y * sheet->bxsize] = COL8_000000;
							}
						}
						sheet_refresh(sheet, 8, 28, 8 + 240, 28 + 128);
						cursor_y = 28;
					} else if (strcmp(cmdline, "dir") == 0) {
						for (x = 0; x < 224; x++) {
							if (finfo[x].name[0] == 0x00) {
								break;
							}
							if (finfo[x].name[0] != 0xe5) {
								if ((finfo[x].type & 0x18) == 0) {
									sprintf(s, "filename.ext   %7d", finfo[x].size);
									for (y = 0; y < 8; y++) {
										s[y] = finfo[x].name[y];
									}
									s[ 9] = finfo[x].ext[0];
									s[10] = finfo[x].ext[1];
									s[11] = finfo[x].ext[2];
									putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
									cursor_y = cons_newline(cursor_y, sheet);
								}
							}
						}
						cursor_y = cons_newline(cursor_y, sheet);
					} else 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++) {
								/* 逐个字符输出 */
								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) {
						putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, "Bad command.", 12);
						cursor_y = cons_newline(cursor_y, sheet);
						cursor_y = cons_newline(cursor_y, sheet);
					}
					putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, ">", 1);
					cursor_x = 16;
				} else {
					if (cursor_x < 240) {
						s[0] = i - 256;
						s[1] = 0;
						cmdline[cursor_x / 8 - 2] = i - 256;
						putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, s, 1);
						cursor_x += 8;
					}
				}
			}
			if (cursor_c >= 0) {
				boxfill8(sheet->buf, sheet->bxsize, cursor_c, cursor_x, cursor_y, cursor_x + 7, cursor_y + 15);
			}
			sheet_refresh(sheet, cursor_x, cursor_y, cursor_x + 8, cursor_y + 16);
		}
	}
}

因为type命令后面会跟文件名,因此这里只比较前4个字符是否为"type"。而在准备文件名部分,会将命令行输入的文件名存入s[]数组中,并转换为大写字母。当前只考虑8个字节的文件名与3个字节的扩展名,因此数组的长度为11。找到文件名后,则将文件的内容逐个字符显示出来,否则显示"File not found"。

尝试用type命令打卡make.bat文件,显示很正常:

而尝试打开二进制文件时,则会显示乱码:

再尝试打开nas汇编代码文件时,效果如下:

可以看到有些字符如"DB","0x55"正常显示出来了,但还是存在大量的乱码。这是由于没有对换行符和制表符的字符编码进行处理导致的。接下来就来增加对换行符和制表符的支持。

制表符和换行符的字符编码如下:

  • 0x09: 制表符
  • 0x0a: 换行符

制表符是用来对齐字符显示位置的。制表符的功能是在当前位置到下一个制表位之间填充上空格。这里作者将制表位设置在0,4,8......等能被4整除的字符数的位置处。对程序做如下的改写:

c 复制代码
else if (strncmp(cmdline, "type ", 5) == 0) 
{
	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++) 
		{
			/* 逐字输出 */
			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);
				}
			}
		}

这里把strcmp换成了strncmp,只比较前5个字符,这样就不需要逐个比较了。

(cursor_x - 8) & 0x1f这一句,用横坐标减8,是去除边框的8个像素;而&0x1f用来判断能否整除32。每个制表符相隔4个字符,每个字符是8个像素,也就是制表符之间相隔32个像素,通过这种方式来判断制表符的位置。

从显示的内容看,仍然有些乱码。不过这是注释的日语部分,这里就暂时忽略了。

2. FAT支持

上面的type命令,其实只能展示512字节以内的文件。对于512字节以上的文件,显示可能会有问题。

这里涉及到Windows的磁盘管理。存放512字节以上的文件时,有时并不存入连续的扇区中,这样我们需要找到文件的下一个扇区。而这一信息,磁盘中是有记录的,找到这一记录就可以正确读取文件内容了。这个记录存放的位置为0柱面,0磁头,2扇区开始的9个扇区,在磁盘映像中的地址为0x000200-0x0013ff。这个记录被称为FAT(file allocation table)。

c 复制代码
......

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

由于磁盘映像中的FAT使用了微软公司的算法进行了压缩,首先需要解压,file_readfat函数即是实现解压的功能:

c 复制代码
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数组中,存放着文件下一个扇区的记录。当文件大小在512字节以上时,存放在多个扇区中。我们从fat数组的第一条记录中获取文件第一个扇区的位置,将这个扇区的内容读取出来;然后再从fat数组的第二条记录中获取文件第二个扇区的位置,再将这个扇区的内容读取出来;......以此类推,每读完一个扇区就去fat数组中查询下一条记录,直到记录中的值为FFF,这表示文件已经读完了。

接下来就通过file_loadfile函数将文件读取出来。从代码中可以看出,文件大小在512字节以上,则根据fat获取下一个扇区的位置,持续读取,直到剩余的内容在512字节以内,直接读出。

c 复制代码
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;
}

3. 应用程序运行

到目前为止我们已经完成了读取文件的功能。应用程序也是一个文件,如何将其运行起来呢?

需要为应用程序创建一个内存段。将应用程序读入进来,跳转到该段中的程序,就可以开始运行了。跳转的方法,在之前多任务的部分已经讲过,是farjump指令。

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 < 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.hrb的应用程序。为了不与windows混淆,这里作者自定义了扩展名.hrb。

我们首先根据文件的大小,通过memman_alloc_4k函数分配一段内存,首地址保存在变量p中。然后调用file_loadfile将hlt.hrb文件读入到这段内存中。接下来,我们设置一个地址段,段号设置为1003(因为1-2号分配给了dsctbl.c使用,3-1002号分配给了前面的多任务mtask.c使用,因此这里使用1003号,当然使用之后的段号也是没问题的),段的首地址设置为刚才p变量中的地址。这样一切准备就绪,我们使用farjmp指令跳转到1003号段,程序就可以开始运行了。

这个hlt.hrb应用程序其实执行的就是HLT指令,因此执行后CPU进入睡眠状态,现象其实就是命令行窗口完全没有反应了。不过还是可以切换到任务A,或者通过鼠标移动任务A的窗口。

下一篇中将会实现应用程序对操作系统功能的调用。敬请期待。

相关推荐
听忆.几秒前
手机屏幕上进行OCR识别方案
笔记
远望清一色10 分钟前
基于MATLAB的实现垃圾分类Matlab源码
开发语言·matlab
confiself20 分钟前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
XiaoLeisj31 分钟前
【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
java·开发语言·java-ee
杜杜的man35 分钟前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*36 分钟前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
半桶水专家36 分钟前
go语言中package详解
开发语言·golang·xcode
llllinuuu37 分钟前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s38 分钟前
Golang--协程和管道
开发语言·后端·golang
Selina K38 分钟前
shell脚本知识点记录
笔记·shell