当用户态的进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。在X86体系中,可以通过两种不同的方式进入系统调用:执行int $0x80汇编命令和执行sysenter汇编命令。后者是Intel在PentiumII中引入的指令,内核从2.6版本开始支持这条命令。本文将集中讨论以int $0x80方式进入系统调用的过程。
通过int $0x80方式调用系统调用实际上是用户进程产生一个中断向量号为0x80的软中断。当用户态进程发出int $0x80指令时,CPU将从用户态切换到内核态并开始执行system_call()。这个函数是通过汇编命令来实现的,它是0x80号软中断对应的中断处理程序。对于所有系统调用来说,它们都必须先进入system_call(),也就是所谓的系统调用处理程序。再通过系统调用号跳转到具体的系统调用服务例程处。
在该函数执行之前,CPU控制单元已经将eflags、cs、eip、ss和esp寄存器的值自动保存到该进程对应的内核栈中。随之,在system_call内部首先将存储在eax寄存器中的系统调用号压入栈中。接着执行SAVE_ALL宏。该宏在栈中保存接下来的系统调用可能要用到的所有CPU寄存器。
/linux/arch/i386/kernel/entry.S 241ENTRY(system_call) 242 pushl %eax # save orig_eax 243 SAVE_ALL
通过GET_THREAD_INFO宏获得当前进程的thread_inof结构的地址;再检测当前进程是否被其他进程所跟踪,也就是thread_inof结构中flag字段的_TIF_SYSCALL_TRACE或_TIF_SYSCALL_AUDIT被置1。如果发生被跟踪的情况则转向相应的处理命令处。
244 GET_THREAD_INFO(%ebp) 245 # system call tracing in operation 246 testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp) 247 jnz syscall_trace_entry
接着,对用户态进程传递过来的系统调用号的合法性进行检查。如果不合法则跳入到syscall_badsys标记的命令处。
248 cmpl $(nr_syscalls), %eax 249 jae syscall_badsys
如果系统调用好合法,则跳入相应系统调用号所对应的服务例程当中,也就是在sys_call_table表中找到了相应的函数入口点。由于sys_call_table表的表项占4字节,因此获得服务例程指针的具体方法是将由eax保存的系统调用号乘以4再与sys_call_table表的基址相加。
当系统调用服务例程结束时,从eax寄存器中获得当前进程的的返回值,并把这个返回值存放在曾保存用户态eax寄存器值的那个栈单元的位置上。这样,用户态进程就可以在eax寄存器中找到系统调用的返回码。
250syscall_call: 251 call *sys_call_table(,%eax,4) 252 movl %eax,EAX(%esp) # store the return value
至此,用户进程进入系统调用的过程大致分析完毕。
前辈,您好!想请教一下,当系统调用过程中会不会发生进程切换?我只知道系统调用时当内核态遇到阻塞会发生进程切换,其他还有哪些情况下在这个过程中会发生,比如时间片到了会不会发生进程切换?在发生进程切换时系统调用可能处于用户态或内核态,他们在切换时是怎么一个流程,保存寄存器的值还是什么操作?如果是系统调用刚好完成,cpu从内核态切换会用户态这个过程会不会发生进程切换?
[回复一下]