系统调用是作为异常处理的,同其它异常处理handler类似,主要分为三步:
1、保存现场环境(保存各寄存器的值到内核堆栈中)
2、调用相应的系统调用处理函数
3、恢复现场环境,并由内核态切换回用户态
可通过两种方式来调用system call:
1、int 0x80
2、sysenter
这里主要讨论第一种方式
以x86为例:
0x80软中断的handler在trap_init中初始化:start_kernel->trap_init->system_call
963 set_system_trap_gate(SYSCALL_VECTOR, &system_call);
system_call在arch/x86/kernel/entry_32.S中定义:
516 # system call handler stub
517 ENTRY(system_call)
518 RING0_INT_FRAME # can't unwind into user space anyway
519 pushl %eax # save orig_eax
520 CFI_ADJUST_CFA_OFFSET 4
521 SAVE_ALL
522 GET_THREAD_INFO(%ebp)
523 # system call tracing in operation / emulation
524 testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
525 jnz syscall_trace_entry
526 cmpl $(nr_syscalls), %eax
527 jae syscall_badsys
528 syscall_call:
529 call *sys_call_table(,%eax,4)
530 movl %eax,PT_EAX(%esp) # store the return value
531 syscall_exit:
532 LOCKDEP_SYS_EXIT
533 DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
534 # setting need_resched or sigpending
535 # between sampling and the iret
536 TRACE_IRQS_OFF
537 movl TI_flags(%ebp), %ecx
538 testl $_TIF_ALLWORK_MASK, %ecx # current->work
539 jne syscall_exit_work
540
541 restore_all:
542 TRACE_IRQS_IRET
1、SAVE_ALL做现场保护
2、call *sys_call_table(,%eax,4)调用相应的系统调用;sys_call_table在arch/x86/kernel/syscall_table_32.S中定义
3、syscall_exit_work恢复现场
主要的处理流程如下图所示:
signal对system call的影响:
有一类系统调用不能立即完成,可能阻塞当前调用进程,将进程状态置为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE。
当进程处于TASK_INTERRUPTIBLE状态,收到其它进程发给它的signal时,内核会将该进程置为TASK_RUNNING,进程可以被调度并重新运行;这样就导致了系统调用还没有完成,并返回 EINTR, ERESTARTNOHAND, ERESTART_RESTARTBLOCK, ERESTARTSYS, 或 ERESTARTNOINTR等错误码
当上述情况出现时,内核根据错误码做不同的处理:
1、结束系统调用,并从系统调用的下一条指令执行
2、重新执行,返回到用户空间后重新执行系统调用指令
3、看情况,如果信号设置了SA_RESTART标志,则重新执行;否则,返回EINTR错误码,并结束系统调用
系统调用的重启:
do_notify_resume->do_signal(arch/x86/kernel/signal.c)
824 /* Did we come from a system call? */
825 if (syscall_get_nr(current, regs) >= 0) {
826 /* Restart the system call - no handlers present */
827 switch (syscall_get_error(current, regs)) {
828 case -ERESTARTNOHAND:
829 case -ERESTARTSYS:
830 case -ERESTARTNOINTR:
831 regs->ax = regs->orig_ax;
832 regs->ip -= 2;
833 break;
834
835 case -ERESTART_RESTARTBLOCK:
836 regs->ax = NR_restart_syscall;
837 regs->ip -= 2;
838 break;
839 }
840 }
regs->ax存放的是系统调用服务函数返回值,regs->orig_ax存放的是系统调用编号;而ip指向系统调用的下一条指令,由于int 0x80/sysenter均为两字节,减2后则ip重新指向int 0x80或sysenter
重启系统调用分两种情况:一种是重新调用原系统调用,另一种是调用restart_syscall系统调用