有趣生活

当前位置:首页>科技>命令行查看系统日期30天自制操作系统day17-day18:命令行窗口以及dir命令

命令行查看系统日期30天自制操作系统day17-day18:命令行窗口以及dir命令

发布时间:2026-07-21阅读(2)

导读day16以及之前,我们完成了多任务管理,内存管理,显示层的管理等多并发操作系统的基础知识点。那么今天day17,我们就来完成操作系统一个操作系统的基本程序....

day16以及之前,我们完成了多任务管理,内存管理,显示层的管理等多并发操作系统的基础知识点。

那么今天day17,我们就来完成操作系统一个操作系统的基本程序:命令行.

或者叫shell,或者叫bash,或者叫cmd,或者叫terminal.或者叫console,tty等

我们想实现类似这样的命令行窗口:

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

比如,这里运行ls命令,看到了结果:当前文件夹下的文件列表

这就是我们今天的目标了。

我们最终完成是这个样子的:

虽然不如上面的明亮行窗口美观,但是有那个意思。

我们先分析一下。

  1. 我们多任务管理器设计完了,那么这个命令行窗口肯定是一个新的任务,那么新任务的名字就叫console_task吧,这个写法就像写task_b_main一样。
  2. 我们多层管理器设计完了,那么这个命令行窗口肯定是一个新的层了,新层作为参数输入给新的任务task_cons就行了。
  3. 任务有了,窗口有了,剩下的就是字符的输入。
  4. 还有就是文件系统。

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是新技术。

那我们就开始吧,会按照这样的顺序展开:

  1. 为命令行建立新任务console_task
  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_tasktask_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个事情:

  1. 当键盘字符为 tab键时,把console设置为激活状态,把task_a设置为非激活
  2. 如何把console设置为激活状态,其实就是把窗口标题颜色从灰色改为蓝色。把task_a的标题颜色设置为灰色
  3. 如果单独改变窗口的标题颜色,不改变窗口的内容颜色,那么最好把绘制标题单独写一个函数,方便修改。

当键盘收到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->fifofifo32_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->fifofifo32_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 0000 到0x0026 8000位置是保存的磁盘的内容呢?

具体复制的代码是:

DSKCACEQU0x00100000; 将软盘中的启动区代码,操作系统代码等保存到0x00100000处DSKCAC0EQU0x00008000; 0x7c00 512,BIOS将软盘的启动区代码复制到了0x7c00处,我们要把从0x8000处的内容,软盘中除了启动区的代码,从内存0x8000开始的代码复制到0x001002ff处BOTPAKEQU0x00280000; 将操作系统的代码存放到0x00280000处,等待执行; bootpack程序复制MOVESI,bootpack; 源地址,操作系统代码asmhead.asm bootpack.c的地址MOVEDI,BOTPAK; 目的地址 0x0028 0000MOVECX,512*1024/4 ; 需要复制的双字数CALLmemcpy ; 调用复制程序; 启动区程序,放在软盘上的程序也要复制一下,复制到DSKCAC处; 首先复制一个扇区,把ipl10.nas编译好的文件复制到DSKCAC的前512字节MOVESI,0x7c00; 源地址,启动区ipl10.nas的代码MOVEDI,DSKCAC; 目的地址,0x0010 0000MOVECX,512/4 ; 传送的双字数CALLmemcpy; 然后把剩下的扇区里的文件,即把除了启动区ipl10.nas的代码复制到0x0010 0200; 也就是说,从0x0010 0200处,到0x0010 0000 0x0200 80*512*18*2-512处; 1440K=80*36*512=5*2*8*4*9*512=45*8*8*512=(32 8 5)*0x8000=0x100000 0x68000=0x168000; 0x0010 0000 0x0016 8000=0x0026 8000 MOVESI,DSKCAC0 512; 源地址 0x8000 512=0x8200MOVEDI,DSKCAC 512; 目的地址 0x0010 0000 512 = 0x0010 0200MOVECX,0MOVCL,BYTE [CYLS] ; 到0x0ff0位置获取读区成功的柱面数IMULECX,512*18*2/4; 用柱面数计算双字数SUBECX,512/4; 减去IPL程序占用的双字数CALLmemcpymemcpy:MOVEAX,[ESI] ;将ESI地址处的内容MOV到EDI地址处的内容ADDESI,4MOV[EDI],EAXADDEDI,4SUBECX,1 ;移动一次,ECX -1JNZmemcpy; 如果ECX 不等于 0 ,则复制下一个字节RET

我们把编译好的操作系统镜像放在了软盘上。

软盘的前512字节存放启动区:ipl10.nas,启动区之后就是asmhead.nas和bootpack.c了

asmhead.nas和bootpack.c就是操作系统镜像haribote.sys了。

上面的代码就是把ipl10.nas复制到了0x0010 0000处,占据0x0010 0000到0x0010 0200-1处的内存地址。

asmhead.nas和bootpack.c被编译成了操作系统镜像文件hariboot.img, 存放在软盘的0x0200位置处。

然后就是把软盘剩下的内容复制到了0x0010 0200处,占据0x0010 0200到0x0026 8000-1的内存地址。

此时:hariboot.img自然也被复制到了0x0010 0200到0x0026 8000的内存里。

注意到:软盘是80个磁道,每个磁道2个头,每个头18个扇区,每个扇区512字节。

所有软盘的容量是80*2*18*512=1440K=0x0016 8000。

所以,我们作为演示用的3个文件,也是被放到软盘里,然后被一并复制到0x0010 0200至 0x0026 8000之间的内存里。

所以,为了找到这些文件FILE INFO,我们就要去0x0010 0200到0x0026 8000之间的内存位置找。

不过,这里我们也只不过是确认了一个大概的空间,具体这3个文件保存到了哪里了?

这个地址就根文件格式有关了,我们不用再去解读文件格式,用二进制方式打开文件,会在0x2600处发现HARIBOTE.SYS等字样,这说明,我们复制的haribote.sys文件的文件信息FILEINFO信息被放在了磁盘的0x2600处,那么就会被我们的复制到0x0010 2600处。

接着看会发现,这3个文件的文件FILEINFO信息都被存放在这里了:

有了以上信息,我们就可以去内存里,把文件名读出来,然后显示了。

其实就是从内存的0x0010 2600处,把文件名读取出来,进行显示,这些主要在命令行中实现,所以,代码要写在命令行里:

#define ADR_DISKIMG0x00100000 // 定义磁盘文件在内存中的开始地址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) {// 如果回车前输入了clsfor (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) {//如果回车前输入了dirfor (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 commandputfonts8_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坐标小于112cursor_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的值为0key_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里的内容。

Copyright © 2024 有趣生活 All Rights Reserve吉ICP备19000289号-5 TXT地图HTML地图XML地图