系统调用
系统调用的用户程序入口定义在lib
下的syscall.c
,在syscall
函数里可以使用嵌入式汇编,先将各个参数分别赋值给EAX
,EBX
,ECX
,EDX
,EDX
,EDI
,ESI
,然后约定将返回值放入EAX
中(把返回值放入EAX
的过程是我们需要在内核中实现的),接着使用int
指令陷入内核,在lib/syscall.c
中实现了如下的syscall
函数:
int
指令接收一个8-Bits的立即数为参数,产生一个以该操作数为中断向量的软中断,其流程分为以下几步(请看看这个流程和之前提到的哪个流程类似。):
查找
IDTR
里面的IDT地址,根据这个地址找到IDT,然后根据IDT找到中断向量的门描述符检查CPL和门描述符的DPL,如果CPL数值上大于DPL,产生#GP异常,否则继续
如果是一个ring3到ring0的陷入操作,则根据
TR
寄存器和GDT,找到TSS在内存中的位置,读取其中的SS0
和ESP0
并装载则向堆栈中压入SS
和ESP
,注意这个SS
和ESP
是之前用户态的数据压入
EFLAGS
,CS
,EIP
若门描述符为Interrupt Gate,则修改
EFLAGS
的IF
位为0对于某些特定的中断向量,压入Error Code
根据IDT表项设置
CS
和EIP
,也就是跳转到中断处理程序执行
中断处理程序执行结束,需要从ring0返回ring3的用户态的程序时,使用iret
指令
iret
指令流程如下
iret
指令将当前栈顶的数据依次Pop至EIP
,CS
,EFLAGS
寄存器若Pop出的
CS
寄存器的CPL数值上大于当前的CPL,则继续将当前栈顶的数据依次Pop至ESP
,SS
寄存器恢复CPU的执行
系统调用的参数传递
每个系统调用至少需要一个参数,即系统调用号,用以确定通过中断陷入内核后,该用哪个函数进行处理;普通 C 语言的函数的参数传递是通过将参数从右向左依次压入堆栈来实现;系统调用涉及到用戶堆栈至内核堆栈的切换,不能像普通函数一样直接使用堆栈传递参数;框架代码使用 EAX
,EBX
等等这些通用寄存器从用戶态向内核态传递参数:
框架代码 kernel/irqHandle.c
中使用了TrapFrame
这一数据结构,其中保存了内核堆栈中存储的 7 个寄存器的值,其中的通用寄存器的取值即是通过上述方法从用戶态传递至内核态,并通过 pushal
指令压入内核堆栈的。
Last updated