常用系统命令,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里的内容。