30天自制操作系统——第十九天系统调用(API)

系统调用

API(application program interface)即应用系统对操作系统功能的调用,也可以称为系统调用(system call)。

今天我们就来实现系统调用的功能,我们先来思考一下WIN10系统是如何实现系统调用的呢?

下图是简化版的Windows系统架构图:

30天自制操作系统——第十九天系统调用(API)_第1张图片

Windows计算机中处理器有两种模式,分别为用户模式(用户态、目态)和内核模式(核心态、内核态、管态、系统模式、管理模式)。在用户模式下,处理器运行用户进程,不能使用特权指令,其中特权指令是指具有特殊权限的指令,一般不直接给用户使用,比如开/关中断指令、内存清零指令、停机指令等。而在内核模式下,处理器运行内核代码,可以使用特权指令。

在WIN10系统中,用户应用程序是通过子系统DLL来调用本地Windows服务的。Windows为用户模式中的应用程序提供一个虚拟地址块,被称为应用程序的用户空间。而应用程序不能直接访问的其余大块的地址被称为系统空间(内核空间)。

实现系统调用,我们需要CPU从用户空间进入到系统空间执行。而使CPU进入系统空间执行,有三种方式:

1.中断:当有来自外部设备的中断请求到来时,CPU会自动转入系统空间执行。(被动)

2.异常:当有执行指令发生异常时,CPU会进入系统空间执行。(被动)

3.自陷(陷入、陷阱):CPU通过自陷指令进入系统空间执行,自陷指令的执行相当于子程序调用,系统调用一般都是通过自陷指令实现的。(主动)

这里我们也是使用类似自陷的方式,来实现系统调用。

显示单个字符的API

我们先来通过API显示单个字符,实现这个功能我们先把需要显示字符编码存入寄存器,然后再让应用程序能够调用cons_putchar函数。这里存在两个问题,一个问题是函数没法接收存在寄存器上的字符编码,再一个问题是我们不知道cons_putchar函数的地址。所以我们先写一个函数_asm_cons_putchar,将寄存器的值推入栈中,再在这个函数中调用cons_putchar函数。

调用结构图如下:

30天自制操作系统——第十九天系统调用(API)_第2张图片

在bootpack.map文件中我们能够查找到cons_putchar函数的地址,将它填入到代码中。

bootpack.map文件:

在这里插入图片描述

我们将地址填入应用程序中,需要注意的是,当应用程序通过API执行CALL指令实现函数调用时,需要加上段号。这里操作系统的段为“2*8”,使用far-CALL,同时指定段和偏移量。

hlt.nas文件:

[BITS 32]
		MOV		AL,'A'       ;这句就是API
		CALL    2*8:0xbe3    ;还有这句
fin:
		HLT
		JMP		fin

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
		AND		EAX,0xff	; 将AH和EAX的高位置0,将EAX置为已存入字符编码的状态
		PUSH	EAX
		PUSH	DWORD [0x0fec]	; 读取内存并PUSH该值
		CALL	_cons_putchar   ; 调用cons_putchar函数
		ADD		ESP,12		; 将栈中的数据丢弃
		RETF   ; 使用RETF指令返回

其中CALL指令与JMP指令类似,是用来调用函数的指令,可以使用RET指令返回调用原先的位置。它会对当前指令的下一条指令进行压栈操作,即将要返回的目标地址PUSH到栈中,从而实现函数返回。

CLL指令相当于:

PUSH xxxxx

JMP xxxxxx。

修改完成,make run一下:

30天自制操作系统——第十九天系统调用(API)_第3张图片

结束应用程序

目前在命令行窗口中输入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]
		MOV		AL,'h'
		CALL    2*8:0xbe8
		MOV		AL,'i'
		CALL    2*8:0xbe8
		MOV		AL,' '
		CALL    2*8:0xbe8
		MOV		AL,'m'
		CALL    2*8:0xbe8
		MOV		AL,'i'
		CALL    2*8:0xbe8
		MOV		AL,'n'
		CALL    2*8:0xbe8
		MOV		AL,'t'
		CALL    2*8:0xbe8
		RETF

make run一下——

30天自制操作系统——第十九天系统调用(API)_第4张图片

不随操作系统版本而改变的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]
		MOV		AL,'h'
	    INT     0x31
		MOV		AL,'i'
		INT     0x31
		MOV		AL,' '
		INT     0x31
		MOV		AL,'m'
		INT     0x31
		MOV		AL,'i'
		INT     0x31
		MOV		AL,'n'
		INT     0x31
		MOV		AL,'t'
		INT     0x31
		RETF

在使用INT指令调用时,CPU认为执行了中断处理,会自动执行CLI禁止中断请求,因此在函数开头加入STI指令允许中断发生。函数中的RETF指令就无法返回,这里改为IRETD指令。IRETD用于从使用32位操作数大小的中断返回,对应的有IRET,用于从使用16位操作数大小的中断返回。

naskfunc.nas节选:

_asm_cons_putchar:
		STI
		PUSH	1
		AND		EAX,0xff	; 将AH和EAX的高位置0,将EAX置为已存入字符编码的状态
		PUSH	EAX
		PUSH	DWORD [0x0fec]	; 读取内存并PUSH该值
		CALL	_cons_putchar
		ADD		ESP,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天自制操作系统——第十九天系统调用(API)_第5张图片

太久没更,有些生疏了。技术也要时时勤拂拭,勿使惹尘埃!

注:本文参照《30天自制操作系统》制作,感谢各位的持续关注。

另附书籍源码(本文源码20_day文件夹):

链接:https://pan.baidu.com/s/1Lb-nWIdTvU0mYDgo0njqtQ
提取码:ghm2

你可能感兴趣的:(操作系统,windows,c++,操作系统)