外贸企业公司网站建设,本地电脑做服务器 建网站,全国门户网站有哪些,绍兴网站制作系统本篇的内容围绕系统调用展开。为了让应用程序能够调用操作系统功能#xff0c;引入了系统调用以及API的概念。首先实现了显示单个字符的API#xff0c;让应用程序通过传递地址的方式进行调用#xff1b;接下来又改进为通过中断的方式进行调用。在此基础上继续实现了显示字符…本篇的内容围绕系统调用展开。为了让应用程序能够调用操作系统功能引入了系统调用以及API的概念。首先实现了显示单个字符的API让应用程序通过传递地址的方式进行调用接下来又改进为通过中断的方式进行调用。在此基础上继续实现了显示字符串的API。 1. 显示单个字符的API传递地址方式调用
应用程序调用操作系统功能称为系统调用(system call)而API是指应用程序与操作系统接口(application program interface)。
首先来实现显示单个字符的API。上一篇中显示字符的程序我们改写成一个函数cons_putchar:
void cons_putchar(struct CONSOLE *cons, int chr, char move)
{char s[2];s[0] chr;s[1] 0;if (s[0] 0x09) { /* tab */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; }}} 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) {cons-cur_x 8;if (cons-cur_x 8 240) {cons_newline(cons);}}}return;
}使用cons_putchar就可以输出单个字符。这样在应用程序中只需要调用cons_putchar就可以了。使用类似于下面的代码
[BITS 32]MOV AL,ACALL (cons_putchar函数地址)fin:HLTJMP fin在汇编语言中CALL指令与JMP指令类似区别在于调用CALL指令时为了能够在执行RET时正确返回需要先将返回的目标地址PUSH到栈中。应用程序无法知道操作系统中函数的地址因此这里不能在应用程序中直接调用cons_putchar函数需要查到cons_putchar函数的地址后填写到应用程序的代码中。 又因为cons_putchar是C语言函数将需要显示的字符通过写入寄存器的方式来进行参数传递函数无法直接接收。因此这里还需要通过一个汇编语言函数将寄存器的值推入栈再调用cons_putchar函数进行显示。
调用cons_putchar函数需要传入cons变量的地址。应用程序当然无法知道这个地址需要操作系统传递。这里将这个地址放在0x0fec中在asm_cons_putchar函数中从0x0fec地址中获取。
void console_task(struct SHEET *sheet, unsigned int memtotal)
{
……struct CONSOLE cons;char cmdline[30];cons.sht sheet;cons.cur_x 8;cons.cur_y 28;cons.cur_c -1;*((int *) 0x0fec) (int) cons;
……_asm_cons_putchar:PUSH 1AND EAX,0xff ; EAX高位置0EAC置为存入字符编码的状态PUSH EAXPUSH DWORD [0x0fec] ; 从0x0fec中读取cons地址并PUSHCALL _cons_putchar ; 调用cons_putchar显示字符ADD ESP,12 ; 丢弃栈中的数据RET到这里还没有完成应用程序还不知道asm_cons_putchar的地址无法对其进行调用。先运行make会生成bootpack.map的文件用文本编辑器打开可以找到如下的行
0x00000BE3 : _asm_cons_putchar这就是asm_cons_putchar函数的地址可以将其填入到应用程序中
[BITS 32]MOV AL,ACALL 0xbe3fin:HLTJMP fin这样就可以运行应用程序了。在命令行中输入hlt并回车却发现QEMU出错关闭了。
这是什么原因呢
原来应用程序在对API执行CALL指令时需要加上段号。我们给应用程序设置的段号为1003而操作系统的段位2不能使用普通的CALL而应使用fat-CALL。与far-JMP指令一样fat-CALL指令需要指定段号和偏移量。相应地RET指令也需要使用RETF来代替。
[BITS 32]MOV AL,ACALL 2*8:0xbe3fin:HLTJMP fin_asm_cons_putchar:PUSH 1AND EAX,0xff ; EAX高位置0EAC置为存入字符编码的状态PUSH EAXPUSH DWORD [0x0fec] ; 从0x0fec中读取cons地址并PUSHCALL _cons_putchar ; 调用cons_putchar显示字符ADD ESP,12 ; 丢弃栈中的数据RETF这样修改之后再运行hlt就可以正常显示了 应用程序完成字符显示之后就进入了HLT循环相当于就这样卡住了。是否能让其回到操作系统继续执行其他命令呢
我们可以将HLT部分改成RET。前文使用JMP来运行应用程序这里也需要改成CALL。 由于CALL调用的程序位于不同的段其实是far-CALL因此对应地也要使用RETF。首先编写一个函数farcall:
_farcall: ;void farcall(int eip, int cs);CALL FAR [ESP 4] ;eip, csRET改写调用应用程序的部分如下
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);farcall(0, 1003 * 8);// 用CALL代替JMPmemman_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;
}不过重新编译之后asm_cons_putchar函数的地址发生了变化对应修改应用程序中的地址
[BITS 32]MOV AL,ACALL 2*8:0xbe8RETF这样运行结束后就可以退回到操作系统了。还可以显示一个简单的字符串
[BITS 32]MOV AL,hCALL 2*8:0xbe8MOV AL,eCALL 2*8:0xbe8MOV AL,lCALL 2*8:0xbe8MOV AL,lCALL 2*8:0xbe8MOV AL,oCALL 2*8:0xbe8RETF2. 显示单个字符的API中断方式调用
如上面所展示的如果修改了操作系统的代码相应函数的地址发生了变化用这种通过地址调用的方式就总要修改应用程序的代码这样会很麻烦。
为了解决这个问题我们回想一下前面介绍过的中断处理函数。IDT中最多可以注册256个函数出了IRQ0-15以外还有很多空闲我们可以从中找一个注册asm_cons_putchar函数。比如选择0x40号
void init_gdtidt(void)
{
……/* IDT设置 */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);……
}这样只需要使用INT 40指令就可以调用asm_cons_putchar函数了应用程序的代码可以修改如下
[BITS 32]MOV AL,hINT 0x40MOV AL,eINT 0x40MOV AL,lINT 0x40MOV AL,lINT 0x40MOV AL,oINT 0x40RETF使用INT指令来调用asm_cons_putchar函数会被视为中断处理会自动调用CLI来禁止中断请求而实际上这里并非中断我们应该允许此时接收中断因此在开头使用STI允许中断请求返回指令也需要使用IRETD
_asm_cons_putchar:STIPUSH 1AND EAX,0xff PUSH EAXPUSH DWORD [0x0fec] CALL _cons_putcharADD ESP,12 ; IRETD这样修改下来应用程序就不用总是随着操作系统的修改而修改了。
3. 显示字符串的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;
}这样前文显示字符串的部分都可以用以上的函数进行替换了。
有了显示字符串的函数如何变成API呢还是采用注册中断函数的方法。不过中断号毕竟还是有限的如果每个函数都注册一个中断号中断号还是很容易就被占满的。这里我们可以仿照BIOS的调用通过传入不同的功能号只用一个INT就可以选择调用多个不同的函数了。 功能号1 显示单个字符(AL 字符编码) 功能号2 显示字符串函数0(EBX 字符串地址) 功能号3 显示字符串1(EBX 字符串地址ECX 字符串长度)
asm_cons_putcha修改为新的函数
_asm_hrb_api:STIPUSHAD ; 用于保存寄存器的PUSHPUSHAD ; 用于向hrb_api传值的PUSHCALL _hrb_apiADD ESP,32POPADIRETD处理函数hrb_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;
}这样应用程序通过传入不同的功能号调用asm_hrb_api函数hrb_api就会根据传入参数来选择不同的函数。再把IDT中的0x40号函数修改成asm_hrb_api函数就可以了。
参数变了应用程序也需要进行改写一下
[INSTRSET i486p]
[BITS 32]MOV EDX,2MOV EBX,msgINT 0x40RETF
msg:DB hello,0运行结果如下 并没有显示出任何内容这是什么原因呢下一篇我们来继续解决敬请期待。