操作系统|30天自制操作系统——第十九天系统调用(API)
系统调用 API(application program interface)即应用系统对操作系统功能的调用,也可以称为系统调用(system call)。
今天我们就来实现系统调用的功能,我们先来思考一下WIN10系统是如何实现系统调用的呢?
下图是简化版的Windows系统架构图:
文章图片
?
Windows计算机中处理器有两种模式,分别为用户模式(用户态、目态)和内核模式(核心态、内核态、管态、系统模式、管理模式)。在用户模式下,处理器运行用户进程,不能使用特权指令,其中特权指令是指具有特殊权限的指令,一般不直接给用户使用,比如开/关中断指令、内存清零指令、停机指令等。而在内核模式下,处理器运行内核代码,可以使用特权指令。
在WIN10系统中,用户应用程序是通过子系统DLL来调用本地Windows服务的。Windows为用户模式中的应用程序提供一个虚拟地址块,被称为应用程序的用户空间。而应用程序不能直接访问的其余大块的地址被称为系统空间(内核空间)。
实现系统调用,我们需要CPU从用户空间进入到系统空间执行。而使CPU进入系统空间执行,有三种方式:
1.中断:当有来自外部设备的中断请求到来时,CPU会自动转入系统空间执行。(被动)
2.异常:当有执行指令发生异常时,CPU会进入系统空间执行。(被动)
3.自陷(陷入、陷阱):CPU通过自陷指令进入系统空间执行,自陷指令的执行相当于子程序调用,系统调用一般都是通过自陷指令实现的。(主动)
这里我们也是使用类似自陷的方式,来实现系统调用。
显示单个字符的API 我们先来通过API显示单个字符,实现这个功能我们先把需要显示字符编码存入寄存器,然后再让应用程序能够调用cons_putchar函数。这里存在两个问题,一个问题是函数没法接收存在寄存器上的字符编码,再一个问题是我们不知道cons_putchar函数的地址。所以我们先写一个函数_asm_cons_putchar,将寄存器的值推入栈中,再在这个函数中调用cons_putchar函数。
调用结构图如下:
文章图片
在bootpack.map文件中我们能够查找到cons_putchar函数的地址,将它填入到代码中。
bootpack.map文件:
文章图片
我们将地址填入应用程序中,需要注意的是,当应用程序通过API执行CALL指令实现函数调用时,需要加上段号。这里操作系统的段为“2*8”,使用far-CALL,同时指定段和偏移量。
hlt.nas文件:
[BITS 32]
MOVAL,'A';
这句就是API
CALL2*8:0xbe3;
还有这句
fin:
HLT
JMPfin
cons_putchar函数的地址保存在内存中,这里保存在BOOTINFO之前的0x0fec。
console.c节选:
void console_task(struct SHEET *sheet, unsigned int memtotal)
{ (略)
cons.sht = sheet;
cons.cur_x =8;
cons.cur_y = 28;
cons.cur_c = -1;
*((int *) 0x0fec) = (int) &cons;
/*cons_putchar函数的地址保存在0x0fec*/
}
我们使用了far-CALL调用_asm_cons_putchar函数,因此需要使用对应的far-RET返回,即最后使用RETF指令返回。
_asm_cons_putchar函数:
_asm_cons_putchar:
PUSH 1
ANDEAX,0xff ;
将AH和EAX的高位置0,将EAX置为已存入字符编码的状态
PUSH EAX
PUSH DWORD [0x0fec] ;
读取内存并PUSH该值
CALL _cons_putchar;
调用cons_putchar函数
ADDESP,12;
将栈中的数据丢弃
RETF;
使用RETF指令返回
其中CALL指令与JMP指令类似,是用来调用函数的指令,可以使用RET指令返回调用原先的位置。它会对当前指令的下一条指令进行压栈操作,即将要返回的目标地址PUSH到栈中,从而实现函数返回。
CLL指令相当于:
PUSH xxxxx
JMP xxxxxx。
修改完成,make run一下:
文章图片
结束应用程序 目前在命令行窗口中输入hlt之后,执行HLT指令就没法继续输入指令了。我们来让应用程序结束后,能返回操作系统。由于需要调用的程序位于不同的段,应该使用far-CALL调用函数,相对的应用程序使用far-RET返回调用,这里只要把HLT指令改为RETF就可以了。
创建一个farcall函数,这个函数与far jmp类似:
naskfunc.nas节选:
_farcall:;
void farcall(int eip, int cs);
CALL FAR [ESP+4];
eip, cs
RET
再将执行HLT指令的地方改为调用farcall,这里应用程序所在的段为“1003*8”。
console.c节选:
void cmd_hlt(struct CONSOLE *cons, int *fat)
{ (略)
if (finfo != 0) {/* 找到文件的情况 */
(略)
farcall(0, 1003 * 8);
/* 调用farcall */
memman_free_4k(memman, (int) p, finfo->size);
} else {/* 没有找到文件的情况 */
(略)
}
(略)
}
由于修改了操作系统代码,要重新查找一下_asm_cons_putchar函数的地址:
文章图片
再将HLT指令改为RETF指令,同时执行一些内容。
hlt.nas节选:
[BITS 32]
MOVAL,'h'
CALL2*8:0xbe8
MOVAL,'i'
CALL2*8:0xbe8
MOVAL,' '
CALL2*8:0xbe8
MOVAL,'m'
CALL2*8:0xbe8
MOVAL,'i'
CALL2*8:0xbe8
MOVAL,'n'
CALL2*8:0xbe8
MOVAL,'t'
CALL2*8:0xbe8
RETF
make run一下——
文章图片
不随操作系统版本而改变的API 我们在IDT中找一个空闲的项,这里选择使用0x31号(0x30 ~ 0xff都是空闲的,随便选一个),再将_asm_cons_putchar函数注册到这里:
dsctbl.c节选:
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 + 0x31, (int) asm_cons_putchar, 2 * 8, AR_INTGATE32);
/* 注册asm_cons_putchar函数*/ return;
}
再将hlt.nas文件中的“CALL 2*8:0xbe8 ”更改为“ INT 0x31”。
hlt.nas文件:
[BITS 32]
MOVAL,'h'
INT0x31
MOVAL,'i'
INT0x31
MOVAL,' '
INT0x31
MOVAL,'m'
INT0x31
MOVAL,'i'
INT0x31
MOVAL,'n'
INT0x31
MOVAL,'t'
INT0x31
RETF
在使用INT指令调用时,CPU认为执行了中断处理,会自动执行CLI禁止中断请求,因此在函数开头加入STI指令允许中断发生。函数中的RETF指令就无法返回,这里改为IRETD指令。IRETD用于从使用32位操作数大小的中断返回,对应的有IRET,用于从使用16位操作数大小的中断返回。
naskfunc.nas节选:
_asm_cons_putchar:
STI
PUSH 1
ANDEAX,0xff ;
将AH和EAX的高位置0,将EAX置为已存入字符编码的状态
PUSH EAX
PUSH DWORD [0x0fec] ;
读取内存并PUSH该值
CALL _cons_putchar
ADDESP,12;
丢弃栈中的数据
IRETD;
这里改为IRETD指令
更改应用程序名称 现在应用程序的名字叫hlt已经不合适了,我们来实现这样的功能:先根据应用程序的名称来寻找对应的文件,如果找到就执行,找不到就提示输入错误”Input error“。
console.c节选:
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, "Input error.", 12);
cons_newline(cons);
cons_newline(cons);
}
}
return;
}
修改cmd_hlt函数,得到cmd_app函数,令输入hi.hre和hi都能够运行程序。再将hlt.nas文件更名为hi.nas,然后汇编生成hi.hrb。
修改完成,执行make run——
文章图片
太久没更,有些生疏了。技术也要时时勤拂拭,勿使惹尘埃!
注:本文参照《30天自制操作系统》制作,感谢各位的持续关注。
另附书籍源码(本文源码20_day文件夹):
【操作系统|30天自制操作系统——第十九天系统调用(API)】链接:https://pan.baidu.com/s/1Lb-nWIdTvU0mYDgo0njqtQ
提取码:ghm2
推荐阅读
- 操作系统|[译]从内部了解现代浏览器(1)
- 你的口红,真的很美!
- 30天健身笔记(45)
- 【V课会】第3季-30天小学思维导图实战营
- 苹果手机如何利用库乐队自制铃声
- 2021-05-06节俭生活第30天,活得不如一条狗通透
- 用了30天整理的一些GO语言学习资料,2019请你加油
- 小程序|【自制壁纸生成器】2022新年壁纸领取,换一张手机壁纸,迎接2022叭~
- 【都市】生日|【都市】生日 [01] 【87】 一鸣30天中篇小说挑战营第二期
- 自制海苔芝麻--孩子拌饭吃