system_call中断处理过程分析

Posted on 2016-03-27 22:42  昆仑雪狐  阅读(303)  评论(0编辑  收藏  举报

 

本文所有的分析内容都是基于Linux3.18.6内核,鉴于对应不同内核版本,系统调用的实现不相同。若需要分析其他版本内核的系统调用的实现过程,请谨慎参考。
system_call函数的功能是用来响应外壳函数发起的0x80中断,当外壳函数通过,其位于arch/x86/kernel/entry_32.S文件内。具体的执行流程,上一篇分析已列出,不再赘述。程序片段如下所示:
ENTRY(system_call)
    RING0_INT_FRAME            # can't unwind into user space anyway
    ASM_CLAC
    pushl_cfi %eax            # save orig_eax
    SAVE_ALL                   
    GET_THREAD_INFO(%ebp)   
                            # system call tracing in operation / emulation
    testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
    jnz syscall_trace_entry
jae syscall_badsys
syscall_call:
    call *sys_call_table(,%eax,4)
syscall_after_call:
    movl %eax,PT_EAX(%esp)        # store the return value
syscall_exit:
    LOCKDEP_SYS_EXIT
    DISABLE_INTERRUPTS(CLBR_ANY)    # make sure we don't miss an interrupt
                    # setting need_resched or sigpending
                    # between sampling and the iret
    TRACE_IRQS_OFF
    movl TI_flags(%ebp), %ecx
    testl $_TIF_ALLWORK_MASK, %ecx    # current->work
    jne syscall_exit_work
 
restore_all:
    TRACE_IRQS_IRET
restore_all_notrace:
#ifdef CONFIG_X86_ESPFIX32
    movl PT_EFLAGS(%esp), %eax    # mix EFLAGS, SS and CS
    # Warning: PT_OLDSS(%esp) contains the wrong/random values if we
    # are returning to the kernel.
    # See comments in process.c:copy_thread() for details.
    movb PT_OLDSS(%esp), %ah
    movb PT_CS(%esp), %al
    andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax
    cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax
    CFI_REMEMBER_STATE
    je ldt_ss            # returning to user-space with LDT SS
#endif
restore_nocheck:
    RESTORE_REGS 4            # skip orig_eax/error_code
irq_return:
    INTERRUPT_RETURN
  • 其中RING0_INT_FRAME是一个宏,在/arch/x86/kernel/entry_32.S文件内。展开如下:
.macro RING0_INT_FRAME
    CFI_STARTPROC simple
    CFI_SIGNAL_FRAME
    CFI_DEF_CFA esp, 3*4
    /*CFI_OFFSET cs, -2*4;*/
    CFI_OFFSET eip, -3*4
.endm
  • ASM_CLAC同样是一个宏,定义在/arch/x86/include/asm/smap.h文件内。作用是加强SMAP保护的作用( Supervisor Mode Access Prevention 管理模式访问保护,intel提供了两条指令STAC和CLAC用于临时打开/关闭这个功能 )。
  • 接下来将eax压栈以保存系统调用号。
  • SAVE_ALL宏展开为:
.macro SAVE_ALL
    cld
    PUSH_GS
    pushl_cfi %fs
    /*CFI_REL_OFFSET fs, 0;*/
    pushl_cfi %es
    /*CFI_REL_OFFSET es, 0;*/
    pushl_cfi %ds
    /*CFI_REL_OFFSET ds, 0;*/
    pushl_cfi %eax
    CFI_REL_OFFSET eax, 0
    pushl_cfi %ebp
    CFI_REL_OFFSET ebp, 0
    pushl_cfi %edi
    CFI_REL_OFFSET edi, 0
    pushl_cfi %esi
    CFI_REL_OFFSET esi, 0
    pushl_cfi %edx
    CFI_REL_OFFSET edx, 0
    pushl_cfi %ecx
    CFI_REL_OFFSET ecx, 0
    pushl_cfi %ebx
    CFI_REL_OFFSET ebx, 0
    movl $(__USER_DS), %edx
    movl %edx, %ds
    movl %edx, %es
    movl $(__KERNEL_PERCPU), %edx
    movl %edx, %fs
    SET_KERNEL_GS %edx
.endm
  • 首先是将寄存器内容压栈,保存当前上下文。接着将_USER_DS内容写入DS(数据段寄存器)和ES(代码段寄存器)两个寄存器。目的是为了减少从内核态返回的开销【1】。
  • 获取thread_info结构的地址,检测thread_info中的标志是否有跟踪。
  • 有跟踪则执行系统跟踪的代码,然后通过将给定的系统调用号与NR_syscalls作比较以检查其有效性。
  • syscall_call是整个代码的关键部分,通过传进来的系统调用号,在系统调用表sys_call_table中取对应的系统调用处理函数,表项以32位存放,所以给调用号乘以4。
  • 调用结束之后,储存处理函数的返回值eax
  • 在返回用户态之前先关中断。
  • syscall_exit_work进行了进程调度、消息传递的工作,进程切换可导致新的中断或系统调用
  • 【work_pending中判断NEED_RESCHED,由结果执行work_resched重新进行进程调度,或进入work_notifysig处理信号】
  • 最后从restore开始执行退出调用的操作。
 
ps:安利一下用的在线流程图绘制网站https://www.processon.com/tour,方便、功能足够。
参考文献

By:昆仑雪狐

原创作品转载请注明出处

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000