系统调用
系统调用的用户程序入口定义在lib下的syscall.c,在syscall函数里可以使用嵌入式汇编,先将各个参数分别赋值给EAX,EBX,ECX,EDX,EDX,EDI,ESI,然后约定将返回值放入EAX中(把返回值放入EAX的过程是我们需要在内核中实现的),接着使用int指令陷入内核,在lib/syscall.c中实现了如下的syscall函数:
int32_t syscall(int num, uint32_t a1,uint32_t a2,
uint32_t a3, uint32_t a4, uint32_t a5)
{
int32_t ret = 0;
uint32_t eax, ecx, edx, ebx, esi, edi;
asm volatile("movl %%eax, %0":"=m"(eax));
asm volatile("movl %%ecx, %0":"=m"(ecx));
asm volatile("movl %%edx, %0":"=m"(edx));
asm volatile("movl %%ebx, %0":"=m"(ebx));
asm volatile("movl %%esi, %0":"=m"(esi));
asm volatile("movl %%edi, %0":"=m"(edi));
asm volatile("movl %0, %%eax"::"m"(num));
asm volatile("movl %0, %%ecx"::"m"(a1));
asm volatile("movl %0, %%edx"::"m"(a2));
asm volatile("movl %0, %%ebx"::"m"(a3));
asm volatile("movl %0, %%esi"::"m"(a4));
asm volatile("movl %0, %%edi"::"m"(a5));
asm volatile("int $0x80");
asm volatile("movl %%eax, %0":"=m"(ret));
asm volatile("movl %0, %%eax"::"m"(eax));
asm volatile("movl %0, %%ecx"::"m"(ecx));
asm volatile("movl %0, %%edx"::"m"(edx));
asm volatile("movl %0, %%ebx"::"m"(ebx));
asm volatile("movl %0, %%esi"::"m"(esi));
asm volatile("movl %0, %%edi"::"m"(edi));
return ret;
}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