[Rx86OS-XVIII] 制作命令行窗口II(命令)

阅读书籍:《30天自制操作系统》—川合秀实[2015.04.22-23]


1 光标闪烁

接受Tab键的是HariMain程序,每个任务控制自己光标的闪烁。


首先是HariMain中的光标,Tab键本身也是在HariMain中被接收的(程序可以将接收到的键盘数据发送到其它内存中);当此次Tab键时切换到其它任务中时,HariMain程序中的窗口不显示光标,再次被Tab切换回到HariMain中时,显示光标。像这样的过程可以使用一个标识变量来实现。


然后是console_task中的光标,在HariMain程序中往console_task的缓冲区中写入光标显示(如2)或不显示(如3)的值。console_task启动时不显示光标,当收到HariMain程序传输过来的显示光标的值时再显示光标。(Harib15a-b)


2 支持回车

程序将命令行窗口划由console_task任务控制。键盘数据由HariMain程序直接接收,首先需要将接收到的回车发送到console_task任务的缓冲区中。同时,在console_task中也要编写判别回车字符到来并作出处理的代码。这里的处理包括将原来位置上的光标用空格擦掉,然后在下一行开头位置显示提示符和光标。


3 支持窗口滚动

一旦有了回车功能或者一直往命令窗口中输入内容,当输入到命令窗口底端的时候,就不能继续输入了。此时,窗口应该支持向下滚动的功能。


实现窗口向下滚动就是将窗口的内容往上移动,覆盖窗口上面的内容,然后将窗口下面腾出来的行抹成窗口的背景色。

if (cursor_y < 28 + 112) {	
	cursor_y += 16;//换行	
} else {	
	//滚动,覆盖窗口上面行的内容	
	for (y = 28; y < 28 + 112; y++) {	
		for (x = 8; x < 8 + 240; x++) {	
			sheet->buf[x + y * sheet->bxsize] = sheet->buf[x + (y + 16) * sheet->bxsize];	
		}	
	}
	//抹黑窗口腾出来的行	
	for (y = 28 + 112; 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);	
}
putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, ">", 1);//提示符

4 支持命令

命令窗口支持命令后就可以将屏幕上显示的内存剩余量,按键编码,光标坐标等都去掉了,反正它们也被命令行窗口挡住了。反正我这里记的也是“书”中的命令。


只要知道console_task任务的缓冲区的地址,在程序的任何一个地方都可以操作console_task的缓冲区。console_task获得键盘数据的方式多多。


首先在console_task程序中判别往命令行窗口中输入的是不是指定的命令。如果是,就调用相应的函数,并将执行后的结果显示在命令行窗口之上;如果不是,则作出不是默认命令的处理。如果要使用只能在HariMain函数中得到的数据,就往console_task的栈内存中传递这个数据,让console_task能够利用。


4.1 mem

mem(memory缩写)用来查询内存使用情况。


首先,将HariMain函数中检测到的总内存量(在没有分配内存时的检测,所以不能再console_task中调用函数来检测内存总量)传递给console_task。然后再在console_task中调用函数来显示内存,并处理非mem命令的操作。


4.2 cls

cls命令是“clearscreen”的缩写,用来清屏。清屏命令的实现比较简单,当判断键盘输入“cls”并回车后,就将命令控制窗口显示的所有内容改变为显示窗口背景色,最后在窗口左上方显示输入提示符。


4.3 dir

dir命令用来显示文件名、文件的日期以及文件的大小。


首先,程序文件本身或者编译器会往文件中隐式地加上文件名、文件大小、文件日期等信息。总之,程序文件经编译器成为最终的可执行文件后会包含一些些文件属性信息。“书”中的可执行文件由3个文件拼凑在一起,这3个文件的文件名等都被保存在磁盘的0柱面、0磁头、1扇区开始的0x002600之后。进入x86保护模式时,将磁盘文件的内容拷贝到内存地址0x00100000~ 0x00267fff处,文件本身的信息就对应到从内存0x00102600开始写入,根据文件正内容的位置(磁盘或内容)就可以知道最多可以保存多少个问价你的信息(这里为244,见IPL开始的几个字节的规定内容,包含这个设置)。


每个文件的信息以32个字节为单位被依次保存(查看磁盘的0x002600偏移处)。用一个结构体来描述这32字节内容。

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

“书”375页对这32个字节有解释。


然后在console_task中来处理“dir”命令。

#define ADR_DISKIMG	0x00100000
struct FILEINFO *finfo = (struct FILEINFO *) (ADR_DISKIMG + 0x002600);
……
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);	
			}	
		}	
	}
}

编写这一段程序要明白文件信息保存的格式(在内存中每个字节内是什么含义)。


4.4 type

(1) 文件在磁盘映像中的地址

type命令跟Linux下的cat命令相似,使用“type文件名”就显示文件中的内容。描述文件信息的结构体的clustno元素表示文件从磁盘上的哪个扇区开始存放。


“书”用二进制编辑器研究磁盘文件,发现了各文件信息中的clustno属性与各文件内容的关系:文件在磁盘映像中的地址= clustno * 512 + 0x003e00(clustno=0的文件的内容从这个地址开始被保存)。


对于存在磁盘中的文件来说,要显示这些文件的内容,首先根据磁盘映像中地址跟内存地址的对应关系。找到各文件内容在内存中的首地址,再根据文件大小,从这段内存中的内容写往屏幕对应的显存中即可。在使用type命令显示文件内容时,需要处理像“制表符”、“回车符”、“换行符”等特殊字符,因为在键盘编码的数组keytable中没有对这些字符进行编码,会显示乱码。


“书”是在375页往磁盘映像中加入ipl10.nas、make.bat、haribote.sys为前提的。也就是说,当把磁盘映像拷贝到内存中后,IPL的汇编代码和make.bat的ASCII的编码被保存到了内存中,二进制文件haribote.sys也被拷贝到了内存中。


(2) 根据FAT读取文件内容

像(1)那样读取保存在磁盘中的数据有问题。


按照Windows管理磁盘的方法,保存大于512字节的文件时,有时候并不是存入连续的扇区中。对于文件的下一段存放在哪里,在磁盘中有记录,只要正确分析这个记录,就可以正确读取文件内容了。这个记录位于磁盘的0柱面、0磁头、2扇区开始的9个扇区中,在磁盘映像中相当于0x000200~ 0x0013ff。这个记录被称为FAT(fileallocation table,文件分配表)。微软在磁盘中放了2份FAT,第一份FAT位于0x000200~ 0x0013ff,第2份位于0x001400~ 0x0025ff。第2份是备份FAT,内容和第一份完全相同。


微软对FAT进行了压缩(Page.391),将FAT数据解压出来后,按照FAT中各号记录得到clustno的值,从而得到文件的下一段内容的地址,读到FAT的表结束的字段为止。欣赏一下结合FAT读保存在磁盘映像中的文件的代码思路。

/* 解压磁盘中的FAT
 * fat是保存被解压的FAT内存的首地址
 * img为FAT在内存中的首地址
 */
void file_readfat(int *fat, unsigned char *img)
{
	int i, j = 0;
	for (i = 0; i < 2880; i += 2) {//通过移位实现FAT解压
		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读取文件内容到缓冲区
* 文件信息(在磁盘0x002600开始处):
* clustno为文件的第一个蔟号,
* size是文件的大小。
* buf是保存文件内容的内存
* fat是经解压后的磁盘的FAT信息
* img是文件内容开始的首地址
*/
void file_loadfile(int clustno, int size, char *buf, int *fat, char *img)
{
	int i;
	for (;;) {
		if (size <= 512) { //如果内容小于512字节
			for (i = 0; i < size; i++) {
				buf[i] = img[clustno * 512 + i];
			}
			break;
		}
		for (i = 0; i < 512; i++) {//内容大于512字节,则还有下一个蔟
			buf[i] = img[clustno * 512 + i];
		}
		size -= 512;
		buf += 512;
		clustno = fat[clustno];//FAT内的文件记录有这个规律
	}
	return;
}
//磁盘映像内容被载入内存中,文件信息在内存中的地址
struct FILEINFO *finfo = (struct FILEINFO *) (ADR_DISKIMG + 0x002600);
……
//磁盘映像内容被载入内存后FAT在内存中的地址
file_readfat(fat, (unsigned char *) (ADR_DISKIMG + 0x000200));
……
//磁盘映像内容被载入内存后文件内容开始的内存地址
file_loadfile(finfo[x].clustno, finfo[x].size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));

代码中ADR_DISKIMG+ xxxxxxxx是“书”在开始的时候根据内存地址分布而自主选择的值。

start、ncst命令p.571-572。


总结

内存成了被CPU分开管理的程序之间联系的桥梁,如多任务。多个任务之间通过访问彼此的内存来实现数据的交互,只需要知道彼此的内存地址即可完成这个操作。这种常见的操作有“通过fifo函数向另一个任务中写数据”、“直接向另一个任务的栈内存中传递参数”等。


在二进制编辑器中可以观看组成二进制文件的每个文件的相关信息。


明确“文件内容在文件中的地址”和“文件内容被拷贝到内存中后的地址”的关系。


[x86OS] Note Over.

[2015.04.29]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值