常用系统命令,30天自制操作系统day17-day18:命令行窗口以及dir命令
常用系统命令,30天自制操作系统day17-day18:命令行窗口以及dir命令虽然不如上面的明亮行窗口美观,但是有那个意思。我们最终完成是这个样子的:在这个窗口里,我们可以运行命令,并且看到结果。比如,这里运行ls命令,看到了结果:当前文件夹下的文件列表这就是我们今天的目标了。
day16以及之前,我们完成了多任务管理,内存管理,显示层的管理等多并发操作系统的基础知识点。
那么今天day17,我们就来完成操作系统一个操作系统的基本程序:命令行.
或者叫shell 或者叫bash 或者叫cmd 或者叫terminal.或者叫console,tty等
我们想实现类似这样的命令行窗口:

在这个窗口里,我们可以运行命令,并且看到结果。

比如,这里运行ls命令,看到了结果:当前文件夹下的文件列表
这就是我们今天的目标了。
我们最终完成是这个样子的:

虽然不如上面的明亮行窗口美观,但是有那个意思。
我们先分析一下。
- 我们多任务管理器设计完了,那么这个命令行窗口肯定是一个新的任务,那么新任务的名字就叫console_task吧 这个写法就像写task_b_main一样。
 - 我们多层管理器设计完了,那么这个命令行窗口肯定是一个新的层了,新层作为参数输入给新的任务task_cons就行了。
 - 任务有了,窗口有了,剩下的就是字符的输入。
 - 还有就是文件系统。
 
3.1 关于字符输入:实现对鼠标,键盘的管理还是有点麻烦的,既然在操作系统主函数这个任务里已经实现了对键盘的解读,就没必要在task_cons里重复解读了,直接在操作系统主函数,也就是任务0中把解码后的键盘字符发送到任务task_cons里就行了,那么此时,task_cons里应该有一个fifo的数组才存储键盘字符。
3.2 关于文件系统:因为通常命令就是a调用程序,b运行程序,c显示结果。其中调用程序就是从磁盘上读取文件内容到内存中;运行程序就是指令指针从文件内容对应的开头内存地址开始运行;显示结果就是还是我们原来的绘制字符用的函数putfonts8_asc_sht. 其实b c步我们已经实现了,就是a还没有实现,这就要求我们要求我们熟悉磁盘上文件的存储结构,这样就可以方便的从磁盘里读取内容到内存了,为此需要建立文件系统。
分析完后,发现内容还是有点多的。
不过还好,1,2,3其实都是已经掌握的技术了,都是对已有技术的应用。4是新技术。
那我们就开始吧,会按照这样的顺序展开:
- 为命令行建立新任务console_task
 - 为命令行建立新窗口
 - 完成字符输入
 - 检索磁盘,实现文件读取
 
任务中,要实现对光标的绘制。
如果要实现对光标的绘制,就要实现一个fifo。
任务中,还要实现休眠和唤醒功能,此时就需要把当前任务的结构体拿出来,然后进行task_sleep(task结构体),所以,还要提前准备一个task_now()函数来取出当前任务的结构体。
任务的输入参数是sheet 这个我们建立窗口后,就有了。
void console_task(struct SHEET *sheet)
{
	struct FIFO32 fifo;// 声明用于本任务的FIFO数组
	struct TIMER *timer;
	struct TASK *task = task_now();// 取得运行本任务的结构体
	int i  fifobuf[128]  cursor_x = 8  cursor_c = COL8_000000;
	fifo32_init(&fifo  128  fifobuf  task);
	timer = timer_alloc();
	timer_init(timer  &fifo  1);
	timer_settime(timer  50);
	for (;;) {
		io_cli();
		if (fifo32_status(&fifo) == 0) {
			task_sleep(task);//休眠当前的任务
			io_sti();
		} else {
			i = fifo32_get(&fifo);
			io_sti();
			if (i <= 1) { //窗口光标的显示逻辑
				if (i != 0) {
					timer_init(timer  &fifo  0); //
					cursor_c = COL8_FFFFFF;// 光标颜色:白色
				} else {
					timer_init(timer  &fifo  1); //
					cursor_c = COL8_000000;// 黑色
				}
				timer_settime(timer  50);// 设定0.5秒的超时器
        // 绘制光标
				boxfill8(sheet->buf  sheet->bxsize  cursor_c  cursor_x  28  cursor_x   7  43);
				// 显示光标
        sheet_refresh(sheet  cursor_x  28  cursor_x   8  44);
			}
		}
	}
}
为命令行建立新窗口并与原窗口切换
    
建立窗口,并把窗口输入到任务console_task内。
  // 准备窗口需要的层,缓冲区
	sht_cons = sheet_alloc(shtctl);
	buf_cons = (unsigned char *) memman_alloc_4k(memman  256 * 165);
	sheet_setbuf(sht_cons  buf_cons  256  165  -1); 
  //在缓冲区绘制名为console的256x165的窗口
	make_window8(buf_cons  256  165  "console"  0);
  // 在窗口内绘制一个240x128的文本框,底色为COL8_000000黑色
	make_textbox8(sht_cons  8  28  240  128  COL8_000000);
  // 从任务控制器里拿出一个空闲任务来运行我们刚才写的任务函数console_task
	task_cons = task_alloc();
	task_cons->tss.esp = memman_alloc_4k(memman  64 * 1024)   64 * 1024 - 8;
	task_cons->tss.eip = (int) &console_task;
	task_cons->tss.es = 1 * 8;
	task_cons->tss.cs = 2 * 8;
	task_cons->tss.ss = 1 * 8;
	task_cons->tss.ds = 1 * 8;
	task_cons->tss.fs = 1 * 8;
	task_cons->tss.gs = 1 * 8;
  // 将窗口层作为任务函数console_task的第一个输入参数
	*((int *) (task_cons->tss.esp   4)) = (int) sht_cons;
  // 以优先级level-2 priority=2来运行任务
	task_run(task_cons  2  2); 
    
到这里,就完成了一个新任务的建立,并且给新任务建立了一个新窗口。
任务运行起来后,还可以在窗口内绘制一个每秒钟闪烁一次的光标。
显示效果如下:
要实现用tab键来切换窗口,要做3个事情:
- 当键盘字符为 tab键时,把console设置为激活状态,把task_a设置为非激活
 - 如何把console设置为激活状态,其实就是把窗口标题颜色从灰色改为蓝色。把task_a的标题颜色设置为灰色
 - 如果单独改变窗口的标题颜色,不改变窗口的内容颜色,那么最好把绘制标题单独写一个函数,方便修改。
 
当键盘收到tab键时:
if (i == 256   0x0f) { /* Tab */
					if (key_to == 0) {
						key_to = 1;
						make_wtitle8(buf_win   sht_win->bxsize   "task_a"   0);
            // 设置console为激活
						make_wtitle8(buf_cons  sht_cons->bxsize  "console"  1);
					} else {
						key_to = 0;
            //设置task_a为激活
						make_wtitle8(buf_win   sht_win->bxsize   "task_a"   1);
						make_wtitle8(buf_cons  sht_cons->bxsize  "console"  0);
					}
					sheet_refresh(sht_win   0  0  sht_win->bxsize   21);
					sheet_refresh(sht_cons  0  0  sht_cons->bxsize  21);
				}
    
可以看到,如果我们按下 tab键, task_a和console的激活状态就会切换。
那么具体的 make_wtitle8里面是啥?其实就是将原来的make_window8成两部分:
void make_window8(unsigned char *buf  int xsize  int ysize  char *title  char act)
{
	boxfill8(buf  xsize  COL8_C6C6C6  0          0          xsize - 1  0        );
	boxfill8(buf  xsize  COL8_FFFFFF  1          1          xsize - 2  1        );
	boxfill8(buf  xsize  COL8_C6C6C6  0          0          0          ysize - 1);
	boxfill8(buf  xsize  COL8_FFFFFF  1          1          1          ysize - 2);
	boxfill8(buf  xsize  COL8_848484  xsize - 2  1          xsize - 2  ysize - 2);
	boxfill8(buf  xsize  COL8_000000  xsize - 1  0          xsize - 1  ysize - 1);
	boxfill8(buf  xsize  COL8_C6C6C6  2          2          xsize - 3  ysize - 3);
	boxfill8(buf  xsize  COL8_848484  1          ysize - 2  xsize - 2  ysize - 2);
	boxfill8(buf  xsize  COL8_000000  0          ysize - 1  xsize - 1  ysize - 1);
	// 用一个函数make_wtitle8来绘制标题部分
  make_wtitle8(buf  xsize  title  act);
	return;
}
// 绘制标题部分的函数,参数 act表示是否激活
void make_wtitle8(unsigned char *buf  int xsize  char *title  char act)
{
	static char closebtn[14][16] = {
		"OOOOOOOOOOOOOOO@" 
		"OQQQQQQQQQQQQQ$@" 
		"OQQQQQQQQQQQQQ$@" 
		"OQQQ@@QQQQ@@QQ$@" 
		"OQQQQ@@QQ@@QQQ$@" 
		"OQQQQQ@@@@QQQQ$@" 
		"OQQQQQQ@@QQQQQ$@" 
		"OQQQQQ@@@@QQQQ$@" 
		"OQQQQ@@QQ@@QQQ$@" 
		"OQQQ@@QQQQ@@QQ$@" 
		"OQQQQQQQQQQQQQ$@" 
		"OQQQQQQQQQQQQQ$@" 
		"O$$$$$$$$$$$$$$@" 
		"@@@@@@@@@@@@@@@@"
	};
	int x  y;
	char c  tc  tbc;
	if (act != 0) {// 如果激活
		tc = COL8_FFFFFF; //字符为白色
		tbc = COL8_000084;// 背景色为蓝色
	} else {
		tc = COL8_C6C6C6;// 字符为灰色
		tbc = COL8_848484;//背景为灰色
	}
	boxfill8(buf  xsize  tbc  3  3  xsize - 4  20);
	putfonts8_asc(buf  xsize  24  4  tc  title);
	for (y = 0; y < 14; y  ) {
		for (x = 0; x < 16; x  ) {
			c = closebtn[y][x];
			if (c == '@') {
				c = COL8_000000;
			} else if (c == '$') {
				c = COL8_848484;
			} else if (c == 'Q') {
				c = COL8_C6C6C6;
			} else {
				c = COL8_FFFFFF;
			}
			buf[(5   y) * xsize   (xsize - 21   x)] = c;
		}
	}
	return;
}
    
这样就完成了窗口的切换,效果如下:
完成字符输入
想给console窗口中输入字符,就必须在console_task函数中进行操作
想在console_task函数中操作呢?console_task就必须有能够接收到键值
所以,console_task中就需要准备一个fifo数组,然后在操作系统主函数中,将拿到的键值给这个fifo数组里放。
具体实现代码如下:
主程序中,将字符发送给task_cons的fifo
if (i < 0x54   256 && keytable[i - 256] != 0) { // 一般键的处理
					if (key_to == 0) {	
						if (cursor_x < 128) {
							s[0] = keytable[i - 256];
							s[1] = 0;
							putfonts8_asc_sht(sht_win  cursor_x  28  COL8_000000  COL8_FFFFFF  s  1);
							cursor_x  = 8;
						}
					} else {	// key_to=1时,将键盘字符发送给task_cons->fifo
						fifo32_put(&task_cons->fifo  keytable[i - 256]   256);
					}
				}
				if (i == 256   0x0e) {// 退格键的处理
					if (key_to == 0) {
						if (cursor_x > 8) {
							putfonts8_asc_sht(sht_win  cursor_x  28  COL8_000000  COL8_FFFFFF  " "  1);
							cursor_x -= 8;
						}
					} else {	// key_to=1时,退格键发送给task_cons->fifo
						fifo32_put(&task_cons->fifo  8   256);
					}
				}
    
然后在task_cons所对应的console_task中添加字符显示代码:
// 命令行的前面输入一个>符号
putfonts8_asc_sht(sheet  8  28  COL8_FFFFFF  COL8_000000  ">"  1);
for(;;){
if (256 <= i && i <= 511) { 
				if (i == 8   256) {// 退格键
					if (cursor_x > 16) {
						putfonts8_asc_sht(sheet  cursor_x  28  COL8_FFFFFF  COL8_000000  " "  1);
						cursor_x -= 8;
					}
				} else {
					//退格键除外的其他键值
					if (cursor_x < 240) {
						s[0] = i - 256;
						s[1] = 0;
						putfonts8_asc_sht(sheet  cursor_x  28  COL8_FFFFFF  COL8_000000  s  1);
						cursor_x  = 8;
					}
				}
			}
      // 在新位置绘制光标
			boxfill8(sheet->buf  sheet->bxsize  cursor_c  cursor_x  28  cursor_x   7  43);
			sheet_refresh(sheet  cursor_x  28  cursor_x   8  44);
}	
    
这样,就完成了在console中输入字符的功能了
不过,当前能够输入的字符其实只有键盘的一部分,我们的命令行窗口需要对键盘上的每个键都进行输入。
那么现在我们来完善一下。
其实主要是shift键按下后要输入一些特殊字符,还有就是大小写的支持
利用shift键支持特殊字符
  ...
  // 制作两个表,分别在shift键是否按下时发挥作用
	static char keytable0[0x80] = {
		0    0    '1'  '2'  '3'  '4'  '5'  '6'  '7'  '8'  '9'  '0'  '-'  '^'  0    0 
		'Q'  'W'  'E'  'R'  'T'  'Y'  'U'  'I'  'O'  'P'  '@'  '['  0    0    'A'  'S' 
		'D'  'F'  'G'  'H'  'J'  'K'  'L'  ';'  ':'  0    0    ']'  'Z'  'X'  'C'  'V' 
		'B'  'N'  'M'  ' '  '.'  '/'  0    '*'  0    ' '  0    0    0    0    0    0 
		0    0    0    0    0    0    0    '7'  '8'  '9'  '-'  '4'  '5'  '6'  ' '  '1' 
		'2'  '3'  '0'  '.'  0    0    0    0    0    0    0    0    0    0    0    0 
		0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0 
		0    0    0    0x5c  0   0    0    0    0    0    0    0    0    0x5c  0   0
	};
	static char keytable1[0x80] = {
		0    0    '!'  0x22  '#'  '$'  '%'  '&'  0x27  '('  ')'  '~'  '='  '~'  0    0 
		'Q'  'W'  'E'  'R'  'T'  'Y'  'U'  'I'  'O'  'P'  '`'  '{'  0    0    'A'  'S' 
		'D'  'F'  'G'  'H'  'J'  'K'  'L'  ' '  '*'  0    0    '}'  'Z'  'X'  'C'  'V' 
		'B'  'N'  'M'  '<'  '>'  '?'  0    '*'  0    ' '  0    0    0    0    0    0 
		0    0    0    0    0    0    0    '7'  '8'  '9'  '-'  '4'  '5'  '6'  ' '  '1' 
		'2'  '3'  '0'  '.'  0    0    0    0    0    0    0    0    0    0    0    0 
		0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0 
		0    0    0    '_'  0    0    0    0    0    0    0    0    0    '|'  0    0
	};
	int key_to = 0  key_shift = 0;// key_shift代表shift键有没有被按下
  for(;;){
    ...
    if (i == 256   0x2a) {	//左shift按下
					key_shift |= 1;
				}
				if (i == 256   0x36) {	//右shift按下
					key_shift |= 2;
				}
				if (i == 256   0xaa) {	//左shift抬起
					key_shift &= ~1;
				}
				if (i == 256   0xb6) {	//右shift抬起
					key_shift &= ~2;
				}
    ...
    if (256 <= i && i <= 511) { 
				sprintf(s  "X"  i - 256);
				putfonts8_asc_sht(sht_back  0  16  COL8_FFFFFF  COL8_008484  s  2);
				if (i < 0x80   256) {
					if (key_shift == 0) {//当没有shift按下时
						s[0] = keytable0[i - 256];
					} else {//当有shift按下时
						s[0] = keytable1[i - 256];
					}
				} else {
					s[0] = 0;
				}
    }
  }
    
这样,我们就完成了特殊字符支持。
那么如果实现大小写支持呢?其实将代码将大写字符 0x20就是小写字符了,不过我们需要在CapsLock键设置后,或者在shift键按下的同时,去决定到底输入大写还是小写。
也就是说,是否输入小写,是CapsLock键和shift键的共同作用的结果。
HariMain(){
	...
  int key_to = 0  key_shift = 0  key_leds = (binfo->leds >> 4) & 7;
	...
	for(;;){
    ...
		if ('A' <= s[0] && s[0] <= 'Z') {	// 如果键值的范围在A-Z内
					if (((key_leds & 4) == 0 && key_shift == 0) || // 如果Capslock灯没亮,shift没有被按下,输入小写字符
							((key_leds & 4) != 0 && key_shift != 0)) {//如果Casplock灯亮,shift被按下,输入小写字符
						s[0]  = 0x20;	// 将字符转换为小写
					}
          ...
				}
    ...
	}
}
    
这里用到了key_leds=(info->leds>>4)&7来或者到Capslock灯信息,然后又用了key_leds&4来判断Capslock灯是否亮。
为什么可以这样判断呢?
键盘上的几个灯:CapsLock NumberLock ScrollLock,分别存储在了info->leds的第6,5,4,位。
利用(info->leds>>4)将7 6 5 4位的值移动到3 2 1 0位,然后 &7,即&0111b 将第3位设置为0,这样key_leds里就剩下CapsLock在第2位,NumberLock在第1位,ScrollLock在第0位。
所以key_leds 与4(0100b)做&后,就可以判断key_leds的第2位是否为1了。
好了,可以输入各种特殊字符和大小写字符了,我们试一下:
检索磁盘,实现文件读取
要完成一个dir 命令,就首先要对读到内存里的磁盘文件进行检索。
要检索文件,就要对资盘上文件存储的方式方法,存储结构有了解。
文件保存在磁盘上,是有固定格式的。
我们现在就是通过文件的固定格式把文件给检索出来。
这个固定格式可以用如下结构体来表示:
struct FILEINFO {
	unsigned char name[8]  ext[3]  type;
	char reserve[10];
	unsigned short time  date  clustno;
	unsigned int size;
};
    
这个结构体一共32个字节,其中
前8个字节name[8] 表示文件名。
第9-11字节ext[3]表示文件的扩展名,
第12个字节type,指文件类型,type取不同的值,代表不同的文件,比如隐藏文件,系统文件等。
第13-22个字节reserve[10]是保留位,一般不写内容,文件比较特殊时,将这些特殊信息写在这里。
第23 24 25 26,一共四个字节分别时time,date 即保存文件的时间,日期。
第27,28存放的是clustno,文件内容在磁盘上的蔟号,即文件内容在磁盘上的地址。
第29,30,31,32位为size表示文件的大小。
通过这样32个字节,我们就可以从磁盘上读取文件内容,并且知道文件的名字,保存日期,文件类型等。
我们在windows上往操作系统里放几个文件,然后尝试去看看,以上格式的文件信息存放在哪里。
首先要在编译操作系统的时候,把3个文件复制到操作系统的镜像img里:
这个在MakeFile里操作:
haribote.img : ipl10.bin haribote.sys Makefile
	$(EDIMG)   imgin:../../../tolset/z_tools/fdimg0at.tek \
		wbinimg src:ipl10.bin len:512 from:0 to:0 \
		copy from:haribote.sys to:@: \
		copy from:ipl10.nas to:@: \
		copy from:make.bat to:@: \
		imgout:haribote.img
    
注意:我们复制了额外的3个文件到操作系统镜像里: haribote.sys ipl10.nas make.bat。
每个文件都有32个字节的数据来保存自己的FILEINFO信息。
当我们加载操作系统的时候,这3个文件就会被复制到内存中。

放在内存的0x0010 0000- 0x00267fff处。具体细节可以到 day03的教程中查看:day03:用C语言写操作系统
有了以上信息,我们就可以去内存里,把文件名读出来,然后显示了。
其实就是从内存的0x0010 2600处,把文件名读取出来,进行显示 这些主要在命令行中实现,所以,代码要写在命令行里:
#define ADR_DISKIMG		0x00100000 // 定义磁盘文件在内存中的开始地址
void console_task(struct SHEET *sheet  unsigned int memtotal)
{
	struct TIMER *timer;
	struct TASK *task = task_now();
	int i  fifobuf[128]  cursor_x = 16  cursor_y = 28  cursor_c = -1;
	char s[30]  cmdline[30];
	struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
	int x  y;
  //存放文件名的开始地址给FILEINFO结构体
	struct FILEINFO *finfo = (struct FILEINFO *) (ADR_DISKIMG   0x002600);
	...
	for (;;) {
		  ...
			if (i == 2) {	
				cursor_c = COL8_FFFFFF;
			}
			if (i == 3) {
				boxfill8(sheet->buf  sheet->bxsize  COL8_000000  cursor_x  cursor_y  cursor_x   7  cursor_y   15);
				cursor_c = -1;
			}
			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) {
					//如果是回车键,
					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) {//如果回车前输入了mem
						// 进行内存检查
						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) {
						// 如果回车前输入了cls
						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) {//如果回车前输入了dir
						for (x = 0; x < 224; x  ) {//遍历所有的文件信息结构体 因为这里最多可以存放225个文件
							if (finfo[x].name[0] == 0x00) {//如果文件名的第一个字符为0,不存在文件,退出
								break;
							}
							if (finfo[x].name[0] != 0xe5) {//如果文件名的第一次字符不是0xe5 说明这里有文件
								if ((finfo[x].type & 0x18) == 0) { //如果文件类型是正常的
									sprintf(s  "filename.ext   }"  finfo[x].size);//把文件大小打印到s中
									for (y = 0; y < 8; y  ) {
										s[y] = finfo[x].name[y]; //把文件名字放到s中
									}
									s[ 9] = finfo[x].ext[0]; // 把文件后缀放到s中
									s[10] = finfo[x].ext[1];
									s[11] = finfo[x].ext[2];
									// 把存放了文件信息的s绘制到窗口里
									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] != 0) {
						// 如果回车前输入了内容,但是这些内容我们没有处理,就输出Bad command
						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);
		}
	}
}
    
第51行至72行,我们对0x0100 2600处的文件信息进行了提取,并显示。
其实,提取文件名字,文件大小挺简单的。就是把文件放到软盘里,然后复制到内存里的那段汇编代码不那么容易理解。
那么在这段代码中,其实,不光完成了对 dir命令的响应,还有对mem cls等命令的响应。
还有实现换行的函数cons_newline(cursor_y sheet)
int cons_newline(int cursor_y  struct SHEET *sheet)
{
	int x  y;
	if (cursor_y < 28   112) {// 如果光标的y坐标小于112
		cursor_y  = 16; // 换行,光标的y坐标加16
	} else {// 如果光标大于112
		// 实现滚动
		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];
			}
		}
    // 把超时112以下的窗口设置为0,即背景
		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);
	}
	return cursor_y;
}
    
所以,cons_newline函数不仅实现了换行,还实现了内容的滚动。
我们再看看具体是如何实现了cls mem dir等命令字符串的传递的。
我们知道,键盘的键值只有在操作系统所在的任务中能够解读,在console_task函数中是不能解读的。但是我们命令行窗口又需要键值,怎么办的?
想办法把键值从操作系统主程序中传递出来。其实这就是进程间的通信。
操作系统本身的进程的信息传递给命令行进程。
如何传递呢?利用FIFO数组。
我们利用对FIFO数组检索,如果传过来的键值不是退格,不是回车,那么就把这个键值存到cmdline中
在用户输入回车时,就对比cmdline和"mem" 对比cmdline和"cls",对比cmdline和"dir",这样就能够去运行相应的命令了。
设置cmdline的代码在上面console_task函数的第87行。
清除命令cls很简单,就是把窗口对应的显示缓存冲区的设为0,然后刷新。
检查内存的命令mem也是之前写的内存管理代码的。
综上,我们一共实现了3个命令:mem cls dir,并且实现了滚动显示。
我们去看一下演示效果:
视频中的光标切换在console_task任务中实现:
void console_task(struct SHEET *sheet  unsigned int memtotal)
{
	// cursor_c=-1,表示光标不显示
	int i  fifobuf[128]  cursor_x = 16  cursor_y = 28  cursor_c = -1;
	//存放文件名的开始地址给FILEINFO结构体
	struct FILEINFO *finfo = (struct FILEINFO *) (ADR_DISKIMG   0x002600);
	...
	for (;;) {
		  ...
			if (i == 2) {
        // 当在操作系统主函数中按下tab键,发送2过来时,
				cursor_c = COL8_FFFFFF;// 把光标的颜色设置为白色,表示光标可以继续显示
			}
			if (i == 3) {//当发送3时
				boxfill8(sheet->buf  sheet->bxsize  COL8_000000  cursor_x  cursor_y  cursor_x   7  cursor_y   15);
				cursor_c = -1;//将光标设置为-1,表示不显示
			}
    	...
  }
  
  }
    
我们去看看操作系统主函数中对tab键的操作:
if (i == 256   0x0f) {	// 如果tab键被按下
					if (key_to == 0) {//如果当前key_to的值为0
						key_to = 1;//就设置key_to=1
            make_wtitle8(buf_win   sht_win->bxsize   "task_a"   0);
            //把console激活
						make_wtitle8(buf_cons  sht_cons->bxsize  "console"  1);
						cursor_c = -1; //把task_a的光标隐藏
						boxfill8(sht_win->buf  sht_win->bxsize  COL8_FFFFFF  cursor_x  28  cursor_x   7  43);
						
            fifo32_put(&task_cons->fifo  2); //给console_task发送2,激活console_task中的光标
					} else {
						key_to = 0;
						make_wtitle8(buf_win   sht_win->bxsize   "task_a"   1);
						make_wtitle8(buf_cons  sht_cons->bxsize  "console"  0);
						cursor_c = COL8_000000; //
						fifo32_put(&task_cons->fifo  3); //
					}
					sheet_refresh(sht_win   0  0  sht_win->bxsize   21);
					sheet_refresh(sht_cons  0  0  sht_cons->bxsize  21);
				}
    
可以看到,光标的调度,在操作系统的主函数中,主函数给console_task发送2,表示要激活console_task中的光标。主函数给console_task 发送3,表示要事任务console_task的光标隐藏。
总结:
至此,我们完成了操作系统中的几个基本的应用程序:命令行。
这个简单的命令行可以实现3个命令:mem cls dir.
不过这3个命令都不是调用的新的并行的任务。而是在命令行任务内部串行执行的。
其实我们也可以写一个命令,可以建立新的命令行窗口出来。
我们建立命令行任务,并没有学习新的知识点,只是用了我们已经准备好的多任务管理器taskctl 多图层管理器sheetctl 以及内存管理器memman。
为了实现dir命令,我们学习了文件保存在磁盘上的结构:32个字节组成的FIFLEINFO结构体。
只要找到磁盘上FIFLINFO结果提存在的位置,就可以遍历处磁盘上的文件名字,文件大小。
今天涵盖了2天的内容day17 day18.
在明天day19,我们会继续完善命令行,在dir命令的基础上,开发出type命令,显示出文件的内容,比如显示出make.bat里的内容。




