河北企业网站设计,深圳网络营销培训,企业网站建设所需要的资料,全国企业信息查询系统登录前言
大家早上好#xff0c;今天我们继续努力哦。 昨天我们已经实现了应用程序的运行, 今天我们来实现由应用程序对操作系统功能的调用(即API, 也叫系统调用)。 为什么这样的功能称为“系统调用”(system call)呢#xff1f;因为它是由应用程序来调用(操作)系统中的功能来完…前言
大家早上好今天我们继续努力哦。 昨天我们已经实现了应用程序的运行, 今天我们来实现由应用程序对操作系统功能的调用(即API, 也叫系统调用)。 为什么这样的功能称为“系统调用”(system call)呢因为它是由应用程序来调用(操作)系统中的功能来完成某种操作, 这个名字很直白吧。 “API” 这个名字就稍微复杂些是“application program interface 的缩写, 即“应用程序(与系统之间的接口”的意思。请大家把这两个名字记住哦考试题目中会有的哦.……开玩笑啦这些其实用不着记啦。 有记这些单词的工夫还不如多享受一下制作操作系统的乐趣呢。 这值得纪念的第一次我们就来做个在命令行窗口中显示字符的API吧。BIOS中也有这个功能哦如果忘了的话请重新看看第二天的内容。怎么样找到了吧无论什么样的操作系统 都会有功能类似的API这可以说是必需的。
一、程序整理
现在这程序是怎么回事下面来改造一下我们操作系统, 让它可以使用API吧… 尤其是console task,简直太不像样了。看着如此混乱的程序代码真是提不起任何干劲来进行改造, 我们还是先把程序整理一下吧。 由于只是改变了程序的写法,并没有改变程序处理的内容因此这里就不讲解了。 从249行改到了85行的console_task, 哦耶
console.c
void console_task(struct SHEET *sheet, unsigned int memtotal)
{struct TIMER *timer;struct TASK *task task_now();struct MEMMAN *memman (struct MEMMAN *) MEMMAN_ADDR;int i, fifobuf[128], *fat (int *) memman_alloc_4k(memman, 4 * 2880);struct CONSOLE cons;char cmdline[30];cons.sht sheet;cons.cur_x 8;cons.cur_y 28;cons.cur_c -1;fifo32_init(task-fifo, 128, fifobuf, task);timer timer_alloc();timer_init(timer, task-fifo, 1);timer_settime(timer, 50);file_readfat(fat, (unsigned char *) (ADR_DISKIMG 0x000200));/* 显示提示符 */cons_putchar(cons, , 1);for (;;) {io_cli();if (fifo32_status(task-fifo) 0) {task_sleep(task);io_sti();} else {i fifo32_get(task-fifo);io_sti();if (i 1) { /* 光标用定时器 */if (i ! 0) {timer_init(timer, task-fifo, 0); /* 下次置 0 */if (cons.cur_c 0) {cons.cur_c COL8_FFFFFF;}} else {timer_init(timer, task-fifo, 1); /* 下次值 1 */if (cons.cur_c 0) {cons.cur_c COL8_000000;}}timer_settime(timer, 50);}if (i 2) { /* 光标ON */cons.cur_c COL8_FFFFFF;}if (i 3) { /* 光标 OFF */boxfill8(sheet-buf, sheet-bxsize, COL8_000000, cons.cur_x, cons.cur_y, cons.cur_x 7, cons.cur_y 15);cons.cur_c -1;}if (256 i i 511) { /* 键盘数据通过任务A */if (i 8 256) {/* 退格键 */if (cons.cur_x 16) {/* 用空格擦除光标后将光标前移一位 */cons_putchar(cons, , 0);cons.cur_x - 8;}} else if (i 10 256) {/* Enter *//* 将光标用空格擦除后换行 */cons_putchar(cons, , 0);cmdline[cons.cur_x / 8 - 2] 0;cons_newline(cons);cons_runcmd(cmdline, cons, fat, memtotal); /* 运行命令 *//* 显示提示符 */cons_putchar(cons, , 1);} else {/* 一般字符 */if (cons.cur_x 240) {/* 显示一个字将之后将光标后移一位 */cmdline[cons.cur_x / 8 - 2] i - 256;cons_putchar(cons, i - 256, 1);}}}/* 重新显示光标 */if (cons.cur_c 0) {boxfill8(sheet-buf, sheet-bxsize, cons.cur_c, cons.cur_x, cons.cur_y, cons.cur_x 7, cons.cur_y 15);}sheet_refresh(sheet, cons.cur_x, cons.cur_y, cons.cur_x 8, cons.cur_y 16);}}
}void cons_putchar(struct CONSOLE *cons, int chr, char move)
{char s[2];s[0] chr;s[1] 0;if (s[0] 0x09) { /* 制表符 */for (;;) {putfonts8_asc_sht(cons-sht, cons-cur_x, cons-cur_y, COL8_FFFFFF, COL8_000000, , 1);cons-cur_x 8;if (cons-cur_x 8 240) {cons_newline(cons);}if (((cons-cur_x - 8) 0x1f) 0) {break; /* 被32整除则break */}}} else if (s[0] 0x0a) { /* 换行 */cons_newline(cons);} else if (s[0] 0x0d) { /* 回车 *//* 先不做操作 */} else { /* 一般字符 */putfonts8_asc_sht(cons-sht, cons-cur_x, cons-cur_y, COL8_FFFFFF, COL8_000000, s, 1);if (move ! 0) {/* move为0时光标不后移 */cons-cur_x 8;if (cons-cur_x 8 240) {cons_newline(cons);}}}return;
}void cons_newline(struct CONSOLE *cons)
{int x, y;struct SHEET *sheet cons-sht;if (cons-cur_y 28 112) {cons-cur_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);}cons-cur_x 8;return;
}void cons_runcmd(char *cmdline, struct CONSOLE *cons, int *fat, unsigned int memtotal)
{if (strcmp(cmdline, mem) 0) {cmd_mem(cons, memtotal);} else if (strcmp(cmdline, cls) 0) {cmd_cls(cons);} else if (strcmp(cmdline, dir) 0) {cmd_dir(cons);} else if (strncmp(cmdline, type , 5) 0) {cmd_type(cons, fat, cmdline);} else if (strcmp(cmdline, hlt) 0) {cmd_hlt(cons, fat);} else if (cmdline[0] ! 0) {/*不是命令也不是空行*/putfonts8_asc_sht(cons-sht, 8, cons-cur_y, COL8_FFFFFF, COL8_000000, Bad command., 12);cons_newline(cons);cons_newline(cons);}return;
}void cmd_mem(struct CONSOLE *cons, unsigned int memtotal)
{struct MEMMAN *memman (struct MEMMAN *) MEMMAN_ADDR;char s[30];sprintf(s, total %dMB, memtotal / (1024 * 1024));putfonts8_asc_sht(cons-sht, 8, cons-cur_y, COL8_FFFFFF, COL8_000000, s, 30);cons_newline(cons);sprintf(s, free %dKB, memman_total(memman) / 1024);putfonts8_asc_sht(cons-sht, 8, cons-cur_y, COL8_FFFFFF, COL8_000000, s, 30);cons_newline(cons);cons_newline(cons);return;
}void cmd_cls(struct CONSOLE *cons)
{int x, y;struct SHEET *sheet cons-sht;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);cons-cur_y 28;return;
}void cmd_dir(struct CONSOLE *cons)
{struct FILEINFO *finfo (struct FILEINFO *) (ADR_DISKIMG 0x002600);int i, j;char s[30];for (i 0; i 224; i) {if (finfo[i].name[0] 0x00) {break;}if (finfo[i].name[0] ! 0xe5) {if ((finfo[i].type 0x18) 0) {sprintf(s, filename.ext %7d, finfo[i].size);for (j 0; j 8; j) {s[j] finfo[i].name[j];}s[ 9] finfo[i].ext[0];s[10] finfo[i].ext[1];s[11] finfo[i].ext[2];putfonts8_asc_sht(cons-sht, 8, cons-cur_y, COL8_FFFFFF, COL8_000000, s, 30);cons_newline(cons);}}}cons_newline(cons);return;
}void cmd_type(struct CONSOLE *cons, int *fat, char *cmdline)
{struct MEMMAN *memman (struct MEMMAN *) MEMMAN_ADDR;struct FILEINFO *finfo file_search(cmdline 5, (struct FILEINFO *) (ADR_DISKIMG 0x002600), 224);char *p;int i;if (finfo ! 0) {/* 找到文件的情况 */p (char *) memman_alloc_4k(memman, finfo-size);file_loadfile(finfo-clustno, finfo-size, p, fat, (char *) (ADR_DISKIMG 0x003e00));for (i 0; i finfo-size; i) {cons_putchar(cons, p[i], 1);}memman_free_4k(memman, (int) p, finfo-size);} else {/* 没有找到文件的情况 */putfonts8_asc_sht(cons-sht, 8, cons-cur_y, COL8_FFFFFF, COL8_000000, File not found., 15);cons_newline(cons);}cons_newline(cons);return;
}void cmd_hlt(struct CONSOLE *cons, int *fat)
{struct MEMMAN *memman (struct MEMMAN *) MEMMAN_ADDR;struct FILEINFO *finfo file_search(HLT.HRB, (struct FILEINFO *) (ADR_DISKIMG 0x002600), 224);struct SEGMENT_DESCRIPTOR *gdt (struct SEGMENT_DESCRIPTOR *) ADR_GDT;char *p;if (finfo ! 0) {/* 找到文件的情况 */p (char *) memman_alloc_4k(memman, finfo-size);file_loadfile(finfo-clustno, finfo-size, p, fat, (char *) (ADR_DISKIMG 0x003e00));set_segmdesc(gdt 1003, finfo-size - 1, (int) p, AR_CODE32_ER);farjmp(0, 1003 * 8);memman_free_4k(memman, (int) p, finfo-size);} else {/* 没有找到文件的情况 */putfonts8_asc_sht(cons-sht, 8, cons-cur_y, COL8_FFFFFF, COL8_000000, File not found., 15);cons_newline(cons);}cons_newline(cons);return;
}file.c
struct FILEINFO *file_search(char *name, struct FILEINFO *finfo, int max)
{int i, j;char s[12];for (j 0; j 11; j) {s[j] ;}j 0;for (i 0; name[i] ! 0; i) {if (j 11) { return 0; /* 没有找到 */ }if (name[i] . j 8) {j 8;} else {s[j] name[i];if (a s[j] s[j] z) {/* 蒋小写字母转换为大写字母 */s[j] - 0x20;} j;}}for (i 0; i max; ) {if (finfo[i].name[0] 0x00) {break;}if ((finfo[i].type 0x18) 0) {for (j 0; j 11; j) {if (finfo[i].name[j] ! s[j]) {goto next;}}return finfo i; /* 找到文件 */}
next:i;}return 0; /* 没有找到 */
}嗯嗯比之前的代码易读多了。你看只要想把代码写得清爽些就一定能做到的连笔者都 做到了嘛(笑)。这个例子说明如果持续增加新的功能一个函数的代码就会变得很长像这 样定期整理一下还是很有帮助的。 好了我们来“make run”输人一些命令试试看。和之前运行的情况一样很好。
二、显示单个字符的API
现在我们要开始做显示单个字符的API了哦。 说起来其实也不是很难, 只要应用程序能用某 种方法调用cons putchar就可以了。 首先我们做一个测试用的应用程序, 将要显示的字符编码存人AL寄存器, 然后调用操作系统的函数, 字符就显示出来了。
[BITS 32]MOV AL,A’CALL (cons_putchar的地址)
fin:HLTJMP fin就是这个样子。CALL是一个用来调用丽数的指令。在C语言中goto和函数调用的处理方式 完全不同不过在汇编语言中CALL指令和JMP指令其实差不多是一码事它们的区别仅仅在 当执行CALL指令时为了能够在接下来执行RET指令时正确返回会先将要返回的目标地 于, 址PUSH到栈中。 关于CALL指令这里想再讲一下。有人可能会想直接写CALLcons putchar不就好了吗?然 而hlt.nas这个应用程序在汇编时并不包含操作系统本身的代码因此汇编器无法得知要调用的 函数地址汇编就会出错。要解决这个问题必须人工查好地址后直接写到代码中。在对haribote.sys进行make的时候, 通过一定的方法我们可以查出cons putchar的地址 没有问题, 那S 么我们就来查一下地址… 且慢!
这样做有个问题, 因为cons_putchar是用C语言写的函数, 即便我们将字符编码存入寄存器 函数也无法接收, 因此我们必须在CALL之前将文字编码推入栈才行, 但这样做也太麻烦了。 没办法我们只好用汇编语言写一个用来将寄 存器的值推入栈的函数了。这个函数不是应用 程序的一部分, 而是写在操作系统的代码中, 因此我们要改写的是naskfunc.nas。 另一方面, 在 应用程序中, 我们CALL的地址不再是cons_putchar, 而是变成了新写的 asm cons_putchar
_asm_cons_putchar:PUSH 1AND EAX,0xff ; 将AH和EAX的高位置0将EAX置为已存入字符编码的状态PUSH EAXPUSH (cons的地址)CALL _cons_putcharADD ESP,12 ; 丢弃栈中数据RET
PUSH的特点是后进先出因此这个程序的顺序没问题。这个12是因为我们push了三次4个字节32位运行时栈的增长是从高地址向低地址大家还记得吧 栈传递Stack Passing
调用函数最常见的方法是将参数依次压入堆栈。调用函数后函数通过堆栈访问这些参数。
push arg3 push arg2 push arg1 call function_name add esp, 12 ; 清理堆栈假设3个参数每个4字节 大家按照这个理解
这段程序的问题在于 “cons的地址 到底是多少。应用程序是不知道这个地址的, 唔,那么只能让操作系统把这个地址事先保存在内存中的某个地方 用程序来指定地址难以实现。哪里比较好呢?对了, 就保存在BOOTINFO之前的0x0fec这个地址吧。 现在操作系统这边的工作已经完成了因此我们先来“ make”一下, 注意这里不是make run。因为应用程序还没有准备好呢所以我们先make。 make完成后, 除了haribote.sys之外, 还会生成一个叫bootpack.map的文件 首然 之前我们一直忽略这个文件的, 不过这次它要派上用场了。 这是一个文本文件用文本编辑器打开其中应该可以找到这样一行: 0x00000BE3 _asm_cons putchar 这就是 _asm_cons putchar 的地址了我们将地址填在应用程序中:
_asm_cons_putchar:PUSH 1AND EAX,0xff ; 将AH和EAX的高位置0将EAX置为已存入字符编码的状态PUSH EAXPUSH (cons的地址)CALL _cons_putcharADD ESP,12 ; 丢弃栈中数据RETvoid console_task(struct SHEET *sheet, unsigned int memtotal)
{...char cmdline[30];cons.sht sheet;cons.cur_x 8;cons.cur_y 28;cons.cur_c -1;*((int *) 0x0fec) (int) cons; /*这里!*/...
}现在操作系统这边的工作已经完成了因此我们先来“ make”一下, 注意这里不是make run。因为应用程序还没有准备好呢所以我们先make。 make完成后, 除了haribote.sys之外, 还会生成一个叫bootpack.map的文件 首然 之前我们一直忽略这个文件的, 不过这次它要派上用场了。 这是一个文本文件用文本编辑器打开其中应该可以找到这样一行: 0x00000BE3 _asm_cons putchar 这就是 _asm_cons putchar 的地址了我们将地址填在应用程序中:
[BITS 32]MOV AL,ACALL 0xbe3
fin:HLTJMP fin然后再进行汇编就可以了很简单吧。
说起来, 我们写的这些代码里面, 哪个部分是API呢“MOVE AL, A’”和“CALL 0xbe3” 就是API了因为API就是由应用程序来使用操作系统所提供的服务。当然我们这个是否达到“服务” 的程度就另当别论了。 现在我们的应用程序也已经完成了, “make run”嘿然后在命令行窗口里面运行 “hlt 就可以了。 啊! qemu.exe出错关闭了看来我们遇了一个不得了的大bug。 在真机环境下无法预料会造成什么后果, 因此请大家不要尝试。下面我们来解决这个bug。
像这样会造成模拟器出错关闭的bug, 果然只有在开发操作系统时才能碰到。 如果不用模拟器进行开发的话不经意间产生的bug有时可能会造成电脑损坏、硬盘被格式化等严重问题, 也许好几天都无法恢复过来。 开发操作系统就是这么刺激。如果通过这次的bug, 大家能够瞥见这种刺激的冰山一角那么这个bug也算是有点用的吧(苦笑)。
不过光扯刺激啦什么的也无济于事, 我们还得仔细寻找原因。哦原来如此找到了 原因其实很简单。应用程序对API执行CALL的时候千万不能忘记加上段号。因此我们不能使用普通的CALL应用程序所在的段为 “1003 * 8, 而操作系统所在的段为“2*8”而应该使用far-CALL。 far-CALL实际上和far-JMP一样只要同时指定段和偏移量即可。
[BITS 32]MOV AL,ACALL 2*8:0xbe3
fin:HLTJMP fin好完工了这样bug应该就解决了, 我们来试试看。 make run然后运行“hlt。还是不行。这次虽然没有出错关闭但qemu.exe停止响应了。 这个问题是由于_asm_cons_putchar的RET指令所造成的。普通的RET指令是用于普通的 CALL 的返回而不能用于far-CALL的返回既然我们用了far-CALL就必须相应地使用far-RET, 也就是RETF指令。因此我们将程序修改一下。
asm_cons_putchar:
(中略)
RETF ;这里!好啦, 这次应该没问题了吧。
3.结束应用程序
照现在这个样子应用程序结束之后会执行HLT我们就无法在命令行窗口中继续输入命令 了这多无聊啊。如果应用程序结束后不执行HLT而是能返回操作系统就好了。 怎样才能实现这样的设想呢?没错只要将应用程序中的HLT改成RET 就可以返回了。相应地操作系统这边也需要用CALL来代替JMP启动应用程序才对。虽说是CALL不过因为要调用的程序位于不同的段, 所以实际上应该使用far-CALL, 因此应用程序那边也应该使用RETF。 我们的方针已经明确了。
C语言中没有用来执行far-CALL的命令我们来创建一个farcall函数这个函数和farjmp大同小异。
_farcall: ; void farcall(int eip, int cs);CALL FAR [ESP4] ; eip, csRET我们还要把hlt命令的处理改为调用farcall。
void cmd_hlt(struct CONSOLE *cons, int *fat)
{ ...if (finfo ! 0) {...farcall(0, 1003 * 8); /* 这里 */} else {}...
}最后我们还要改写一下应用程序hlt.nas, 把HLT换成RETF就可以了。
[BITS 32]MOV AL,ACALL 2*8:0xbe3RETF完工了哦。好, 我们来 make run 然后运行“hlt”。貌似是有bug (今天我们碰了好几个钉子了嘛)。 qemu.exe又停止响应了, 明白了。由于我们改写了操作系统的代码, 怎么回事呢?导致asm_cons_putchar的地址 发生了变化。重新查看bootpack.map我们发现地址变成了这样: 0x0000BE8 : asm_cons putchar 因此, 我们把应用程序的地址修改一下
[BITS 32]MOV AL,ACALL 2*8:0xbe8RETFmake run, “hlt怎么样好了成功了!
趁热打铁, 我们再来个新的尝试: hello” 显示
貌似用循环比较好呢?算了实在太麻烦(笑)。我们运行一下试试看 结果如下。
话说回来现在这个应用程序已经和当初“hlt这个名字完全对不上号了 看来我们得赶快给它改改名字了哦。
4.不随操作系统版本而改变的API
所以说我们又要改写console.c。等等如果修改了操作系统的代码岂不是asm_cons_ putchar的地址也会像上次那样发生变化难道说每次我们修改操作系统的代码都得把应用程序的代码也改一遍?这也太麻烦了。
虽说确实有的操作系统版本一改变应用程序 也得重新编译,不过还有些系统即便版本改变应用程序也照样可以运行大家觉得哪种更好呢?
把这个搞定之后 我们再考虑命名的事。 解决这个问题的方法其实有很多这里先为大家介绍其中一种。 CPU中有个专门用来注册函数的地方也许大家一下子想不起来其实是中断处理 程序。在前面我们曾经做过“当发生IRQ-1的时候调用这个函数”这样的设置, 大家还记得吗? 这是在IDT中设置的。 而CPU用于通知异常状态的中断最多也只有32种这些都在CPU规格说 反正IRQ只有015, 明书中有明确记载。 不过IDT中却最多可以设置256个函数因此还剩下很多没有使用的项。 我们的操作系统从这些项里面借用一个的话, CPU应该也不会有什么意见的吧。所以我们就从IDT中找一个空闲的项来用一下。好我们就选0x40号(其实0x30~0xff都是空闲的只要在这个范围内任意一个都可以)并将_asm_ cons_putchar注册在这里。
void init_gdtidt(void)
{...set_gatedesc(idt 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);set_gatedesc(idt 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);set_gatedesc(idt 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);set_gatedesc(idt 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);set_gatedesc(idt 0x40, (int) asm_cons_putchar, 2 * 8, AR_INTGATE32);return;
}我们只要用INT 0x40来代原来的CALL 2*8:0xbd1就可以调用_asm_ cons_putchar了。这样一来很方便吧?我们来修改一下应用程序吧。
[BITS 32]MOV AL,hINT 0x40MOV AL,eINT 0x40MOV AL,lINT 0x40MOV AL,lINT 0x40MOV AL,oINT 0x40RETF于是程序变成了这个样子。看到这里直觉敏锐的你也许已经发现了“跟调用BIOS的时候差不多嘛….” 虽然INT号不同但通过INT方式调用这一点的确是非常类似。说起来 没错MS-DOS的API采用的也是这种INT方式。 另外使用INT指令来调用的时候会被视作中断来处理, 需要使用 IRETD指令用RETF是无法返回的, 我们还要改写_asm_ cons_putchar。
_asm_cons_putchar:STIPUSH 1AND EAX,0xff ; 将AH和EAX的高位置0将EAX置为已存入字待编码的状态PUSH EAXPUSH DWORD [0x0fec] ; 读取内存并PUSH该值CALL _cons_putcharADD ESP,12 ; 丢弃栈中的数据IRETD ;这里用INT调用时对于CPU来说相当于执行了中 断处理程序因此在调用的同时CPU会自动执 但我们只是用它来代替CALL使用这种做法就显得画蛇添足了。 行CLI指令来禁止中断请求。 我们可不想看到“API处理时键盘无法输入”这样的情况, 因此需要在开头添加一条STI指令。 对于这种问题一般来说可以通过在注册到IDT时修改设置来禁止CPU擅自执行CLI, 其实, 最近貌似懒到家了得反省一下。 不过这个有点麻烦, 还是算了吧(笑)。
make run → 结果如下
你看用这种方法还能把应用程序缩小。这是因为far-CALL指令需要7个字节而INT指令只需要2个字节的缘故。 这次修改还真是一箭双雕呢。
5.应用程序自由命名
现在我们的应用程序只能用hit这个名字,下面我们来让系统支持其他应用程序名这次我们就用hello吧。 将console.c中的 “hlt” 改成“hello, 好啦, 这样我们就可以用hello这个应用程序 …··.!别生气别生气开个玩笑而已()。 好吧, 我们先来改写cons runcmd。
void cons_runcmd(char *cmdline, struct CONSOLE *cons, int *fat, unsigned int memtotal)
{if (strcmp(cmdline, mem) 0) {cmd_mem(cons, memtotal);} else if (strcmp(cmdline, cls) 0) {cmd_cls(cons);} else if (strcmp(cmdline, dir) 0) {cmd_dir(cons);} else if (strncmp(cmdline, type , 5) 0) {cmd_type(cons, fat, cmdline);} else if (cmdline[0] ! 0) {if (cmd_app(cons, fat, cmdline) 0) {/*从此开始*//*不是命令不是应用程序也不是空行*/putfonts8_asc_sht(cons-sht, 8, cons-cur_y, COL8_FFFFFF, COL8_000000, Bad command., 12);cons_newline(cons);cons_newline(cons);}}/*到此结束 */return;
}总结一下修改的地方首先是去掉了cmd_hlt, 并创建了新的cmd_app。 这个函数用来根据命令行的内容判断文件名,并运行相应的应用程序 如果找到文件则返回1没有找到文件则返回0。 现在程序的工作过程是:当输入的命令不是me m、cls、dir、type其中之一时则调用cmd_app, 如果返回0则作为错误处理。 这样应该能行。 我们在cmd_hlt的基础上稍作修改后得到cmd_app函数 具体内容如下。
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{struct MEMMAN *memman (struct MEMMAN *) MEMMAN_ADDR;struct FILEINFO *finfo;struct SEGMENT_DESCRIPTOR *gdt (struct SEGMENT_DESCRIPTOR *) ADR_GDT;char name[18], *p;int i;/*根据命令行生成文件名*/for (i 0; i 13; i) {if (cmdline[i] ) {break;}name[i] cmdline[i];}name[i] 0; /*暂且将文件名的后面置为0*//* 寻找文件 */finfo file_search(name, (struct FILEINFO *) (ADR_DISKIMG 0x002600), 224);if (finfo 0 name[i - 1] ! .) {/*由于找不到文件故在文件名后面加上“.hrb”后重新寻找*/name[i ] .;name[i 1] H;name[i 2] R;name[i 3] B;name[i 4] 0;finfo file_search(name, (struct FILEINFO *) (ADR_DISKIMG 0x002600), 224);}if (finfo ! 0) {/*找到文件的情况*/p (char *) memman_alloc_4k(memman, finfo-size);file_loadfile(finfo-clustno, finfo-size, p, fat, (char *) (ADR_DISKIMG 0x003e00));set_segmdesc(gdt 1003, finfo-size - 1, (int) p, AR_CODE32_ER);farcall(0, 1003 * 8);memman_free_4k(memman, (int) p, finfo-size);cons_newline(cons);return 1;}/*没有找到文件的情况*/return 0;
}我们在程序上动了一点脑筋使得无论输入“hlt” 还是”“hlt.hrb” 都可以启动。因为在Windows命令行窗口中不管加不加后面的 .exe都可以运行程序所以我们就借鉴了这个设计。
差不多完工了我们将hlt.nas改名为hello.nas, 然后汇编生成hello.hrb。 接下来 make run 用dir命令确认一下磁盘中的内容,再输入“hello”。 ha!出来了成功了!
不错!我们再来输人“hlt”试一下,这个文件现在已经没有了, 会不会报错呢?另外, 嗯, 如果输入hello.hrb” 能否正常运行呢?我们来试试看。
出现错误信息了, 加上扩展名的情况也可以太完美了。
6.小心寄存器
hello.hrb的大小现在是21个字节能不能再让它变小点呢?我们做了如下修改用了一个循环。
[INSTRSET i486p]
[BITS 32]MOV ECX,msg
putloop:MOV AL,[CS:ECX]CMP AL,0JE finINT 0x40ADD ECX,1JMP putloop
fin:RETF
msg:DB hello,0改成这样后make一下hello.hrb变成了26个字节居然还大了5个字节哎好失望。不过 这样改也有好处即便以后要显示很长的字符符程序也不会变得太大。
为啥只显示出一个呢? 再把hello.nas仔细检查一遍也没发现什么不对劲的地方啊··· 那问题肯定出在操作系统身上。 既然应用程序没问题, 不过到底是哪里有问题呢刚刚找到了点眉目我们给_asm_cons_putchar添上2行代码就是PUSHAD和POPAD。
_asm_cons_putchar:STIPUSHAD ; 这里!PUSH 1AND EAX,0xff ; PUSH EAXPUSH DWORD [0x0fec] ; CALL _cons_putcharADD ESP,12 ; POPAD ; 这里!IRETD为什么要这么改我们待会儿再讲先来试验一下。
果然是这个问题呀。 那为什么会想到加上PUSHAD和POPAD呢?因为推测这有可能是 INT 0x40之后ECX寄存器的值发生了变化所导致的应该是_cons_putchar改动了ECX的值。因此我们加上了PUSHAD和POPAD确保可以将全部寄存器的值还原这样程序就能正常运行了。
7.用API显示字符串
能显示字符申的API远比只能显示单个字符的API要来的方便, 从实际的应用程序开发角度来说因为一次显示一串字符的情况比一次只显示个字符的情况多得多。从其他操作系统的显示字符申的API来看一般有两种方式:一种是显示一串字符遇到字符编码0则结束另一种是先指定好要显示的字串的长度再显示。我们到底要用哪一种呢 再三考虑之后我们打算同时实现两种方式(笑)。
void cons_putstr0(struct CONSOLE *cons, char *s)
{for (; *s ! 0; s) {cons_putchar(cons, *s, 1);}return;
}void cons_putstr1(struct CONSOLE *cons, char *s, int l)
{int i;for (i 0; i l; i) {cons_putchar(cons, s[i], 1);}return;
}哦对了, 有了这个函数就可以简化mem、 dir、type这几个命令的代码趁着还没忘记, 赶紧改良一下。
void cons_runcmd(char *cmdline, struct CONSOLE *cons, int *fat, unsigned int memtotal)
{if (strcmp(cmdline, mem) 0) {cmd_mem(cons, memtotal);} else if (strcmp(cmdline, cls) 0) {cmd_cls(cons);} else if (strcmp(cmdline, dir) 0) {cmd_dir(cons);} else if (strncmp(cmdline, type , 5) 0) {cmd_type(cons, fat, cmdline);} else if (cmdline[0] ! 0) {if (cmd_app(cons, fat, cmdline) 0) {/*不是命令不是应用程序也不是空行*/cons_putstr0(cons, Bad command.\n\n);/* 这里 */}}return;
}void cmd_mem(struct CONSOLE *cons, unsigned int memtotal)
{struct MEMMAN *memman (struct MEMMAN *) MEMMAN_ADDR;char s[60];/*从此开始*/sprintf(s, total %dMB\nfree %dKB\n\n, memtotal / (1024 * 1024), memman_total(memman) / 1024);cons_putstr0(cons, s);/*到此结束*/return;
}void cmd_dir(struct CONSOLE *cons)
{struct FILEINFO *finfo (struct FILEINFO *) (ADR_DISKIMG 0x002600);int i, j;char s[30];for (i 0; i 224; i) {if (finfo[i].name[0] 0x00) {break;}if (finfo[i].name[0] ! 0xe5) {if ((finfo[i].type 0x18) 0) {sprintf(s, filename.ext %7d\n, finfo[i].size);for (j 0; j 8; j) {s[j] finfo[i].name[j];}s[ 9] finfo[i].ext[0];s[10] finfo[i].ext[1];s[11] finfo[i].ext[2];cons_putstr0(cons, s);/*这里!*/}}}cons_newline(cons);return;
}void cmd_type(struct CONSOLE *cons, int *fat, char *cmdline)
{struct MEMMAN *memman (struct MEMMAN *) MEMMAN_ADDR;struct FILEINFO *finfo file_search(cmdline 5, (struct FILEINFO *) (ADR_DISKIMG 0x002600), 224);char *p;if (finfo ! 0) {p (char *) memman_alloc_4k(memman, finfo-size);file_loadfile(finfo-clustno, finfo-size, p, fat, (char *) (ADR_DISKIMG 0x003e00));cons_putstr1(cons, p, finfo-size);/*这里!*/memman_free_4k(memman, (int) p, finfo-size);} else {cons_putstr0(cons, File not found.\n);/*这里!*/}cons_newline(cons);return;
}代码缩减了12行什么嘛!一开始就这样写不就好了吗?不过不管怎么说也算是个值得高兴 的事吧。 在上面字符串中我们使用了“\n” 这个新的符号, 这里来讲解一下。在C语言中, “\”这个 字符有特殊的含义用来表示一些特殊字符。 这里出现的“\n” 代表换行符即0x0a也就是说用2个字符来表示1个字节的信息 有点怪吧。此外还有 “\t”, 它代表制表符, 即0x09。顺便说一 下, 换行符“\n” 之所以用“n”是因为它是“new line 的缩写。
我们已经有了cons_putstr0 和 cons_putstr1那么怎样把它们变成API呢最简单的方法就是像显示单个字符的API那样, 分配INT0x41和INT0x42来调用这两个函数。 不过这样一来, 只能设置256个项目的IDT很快就会被用光。 既然如此我们就借鉴BIOS的调用方式 在寄存器中存人功能号, 使得只用1个INT就可以用来选择调用不同的函数。 在BIOS中, 存放功能号的寄存器一般是AH, 我们也可以照搬, 但这样最多只能设置256个API函数。而如果我们改用EDX来存放功能号, 就可以设置多达42亿个API函数。这样总不会不够用了吧。
功能号暂时按下面那样划分, 寄存器的用法也是随意设定的, 如果不喜欢的话尽管修改就好哦。
功能号1………….显示单个字符(AL字符编码) 功能号2… 显示字符串0(EBX字符串地址) 功能号3………显示字符串1(EBX字符串地址ECX字符串长度) 接下来我们将_asm_cons_putchar改写成一个新的函数。
_asm_hrb_api:STIPUSHAD ; 用于保存寄存器值的PUSHPUSHAD ; 用于向hrb_api传值的PUSHCALL _hrb_apiADD ESP,32POPADIRETD这个函数非常短, 因为我们想尽量用C语言来编写API处理程序 而且这样大家也更容易理解。 用C语言编写的API处理程序如下:
void hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{struct CONSOLE *cons (struct CONSOLE *) *((int *) 0x0fec);if (edx 1) {cons_putchar(cons, eax 0xff, 1);} else if (edx 2) {cons_putstr0(cons, (char *) ebx);} else if (edx 3) {cons_putstr1(cons, (char *) ebx, ecx);}return;
}嗯还是挺好理解的吧。开头的寄存器顺序是按照PUSHAD的顺序写的,如果在_asm_hrb_api 中不用PUSHAD,而是个一个分别去PUSH的话那当然可以按照自己喜欢的顺序来。 啊对了对了我们还得改一下IDT的设置将INT 0x40改为调_asm_hrb_api。
void init_gdtidt(void)
{...set_gatedesc(idt 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);set_gatedesc(idt 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);set_gatedesc(idt 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);set_gatedesc(idt 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);set_gatedesc(idt 0x40, (int) asm_hrb_api, 2 * 8, AR_INTGATE32);return;
}这样改写之后, 现在的hello.nas就无法正常运行了, 因为我们需要往EDX里面存人1才能调用相应的API。虽说我们加上一条向EDX中存入1的指令就可以 不过既然已经写好了cons_putstr0, 那就于脆用这个新的API写一个hello2.nas吧。
[INSTRSET i486p]
[BITS 32]MOV EDX,2MOV EBX,msgINT 0x40RETF
msg:DB hello,0完工了, 好, 赶紧 运行 hello2” 试试看。 make run
……貌似失败了怎么回事昵今天已经很累了 脑子都不转了我们还是明天再来找原因吧。总之我们先将这个放在一边在以前的hello.nas中加一条EDX 1;试试看吧。
[INSTRSET i486p]
[BITS 32]MOV ECX,msgMOV EDX,1 这里
putloop:MOV AL,[CS:ECX]CMP AL,0JE finINT 0x40ADD ECX,1JMP putloop
fin:RETF
msg:DB hello,0成功了, 总算稍稍松了口气。 今天我们在最后的最后碰了个大钉子(就是hello2)心情有点不爽 不过已经困得不行了, 就先到这吧!大家明天见。
总结
今天我们在最后的最后碰了个大钉子(就是hello2)心情有点不爽 不过已经困得不行了, 就先到这吧! 祝大家元宵节快乐团团圆圆巳巳如意 我们明天见