博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一针见血之系统调用(下)
阅读量:2256 次
发布时间:2019-05-09

本文共 5163 字,大约阅读时间需要 17 分钟。

上一篇文章中我们已经对整个系统调用从用户态到内核态的过程做了一个大概的说明。为了更加深入的理解,我们这篇文章再为大家展现在内核态系统调用的具体是怎么实现的,在这篇文章中我们直接把内核系统调用的源码拿出来进行分析,让大家更容易理解。

内核中的系统调用部分如下图的红色方框里所示。

这里写图片描述

在用户态的代码中我们可以看到在用户态下是通过中断向量int 0x80跳转到内核态sys_call里面的,下面我们进入到linux3.18.6内核里去看看系统调用的代码 。我们找到sys_call,代码如下:

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    cmpl $(NR_syscalls), %eax    jae syscall_badsyssyscall_call:    call *sys_call_table(,%eax,4)syscall_after_call:    movl %eax,PT_EAX(%esp)      # store the return valuesyscall_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_workrestore_all:    TRACE_IRQS_IRETrestore_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#endifrestore_nocheck:    RESTORE_REGS 4          # skip orig_eax/error_codeirq_return:    INTERRUPT_RETURN.section .fixup,"ax"ENTRY(iret_exc)    pushl $0           # no error code    pushl $do_iret_error    jmp error_code.previous    _ASM_EXTABLE(irq_return,iret_exc)#ifdef CONFIG_X86_ESPFIX32    CFI_RESTORE_STATEldt_ss:#ifdef CONFIG_PARAVIRT    /*     * The kernel can't run on a non-flat stack if paravirt mode     * is active.  Rather than try to fixup the high bits of     * ESP, bypass this code entirely.  This may break DOSemu     * and/or Wine support in a paravirt VM, although the option     * is still available to implement the setting of the high     * 16-bits in the INTERRUPT_RETURN paravirt-op.     */    cmpl $0, pv_info+PARAVIRT_enabled    jne restore_nocheck#endif/* * Setup and switch to ESPFIX stack * * We're returning to userspace with a 16 bit stack. The CPU will not * restore the high word of ESP for us on executing iret... This is an * "official" bug of all the x86-compatible CPUs, which we can work * around to make dosemu and wine happy. We do this by preloading the * high word of ESP with the high word of the userspace ESP while * compensating for the offset by changing to the ESPFIX segment with * a base address that matches for the difference. */#define GDT_ESPFIX_SS PER_CPU_VAR(gdt_page) + (GDT_ENTRY_ESPFIX_SS * 8)    mov %esp, %edx          /* load kernel esp */    mov PT_OLDESP(%esp), %eax   /* load userspace esp */    mov %dx, %ax            /* eax: new kernel esp */    sub %eax, %edx          /* offset (low word is 0) */    shr $16, %edx    mov %dl, GDT_ESPFIX_SS + 4 /* bits 16..23 */    mov %dh, GDT_ESPFIX_SS + 7 /* bits 24..31 */    pushl_cfi $__ESPFIX_SS    pushl_cfi %eax          /* new kernel esp */    /* Disable interrupts, but do not irqtrace this section: we     * will soon execute iret and the tracer was already set to     * the irqstate after the iret */    DISABLE_INTERRUPTS(CLBR_EAX)    lss (%esp), %esp        /* switch to espfix segment */    CFI_ADJUST_CFA_OFFSET -8    jmp restore_nocheck#endif    CFI_ENDPROCENDPROC(system_call)

代码分析

整个系统调用的sys_call代码都是用汇编写的,下面我们就通过对代码中的一些汇编指令进行讲解,他也是一种特殊的中断,也需要保存现场和恢复现场的。

上述代第一行代码将寄存器%eax的值保存到当前进程内核态栈的栈顶。由上前面一篇博客可知寄存器%eax保存了被请求系统调用的系统调用号。

SAVE_CALL:

他是用来保存现场的,他是一个宏,SAVE_ALL宏可以在栈中保存中断处理程序可能会使用的所有CPU寄存器。

GET_THREAD_INFO(%ebp)代码调用宏定义GET_THREAD_INFO获取当前进程的进程描述符中的成员变量thread_info的地址,并将该地址保存到寄存器%ebp中。

第10~11行代码

cmpl $(NR_syscalls), %eaxjae syscall_badsys

这两句是判断传递进来的系统调用号是否合法,即是否超出了内核实现的系统调用的最大系统调用号。如果大于,说明系统调用号不合法,跳转到标号syscall_badsys指示的代码段进行出错处理;否则顺序执行。

第13~14行代码

syscall_call:    call *sys_call_table(,%eax,4)

该处代码根据系统调用号(保存在寄存器%eax中)调用具体的系统调用处理函数,该函数的地址保存在存储单元”sys_call_table+4*%eax”中。sys_call_table是系统调用表,注意,我们这里%eax的值为20,也就是调用系统调用处理函数sys_getpid()。

第16~24行代码

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 //退出系统调用之前,检查是否需要处理信号

上述代码是为了能过处理内核中进程的信号量问题。在系统调用或者中断,异常返回到用户空间之前内核都会检查是否有信号在当前进程中挂起。如果有信号就去处理。

第28~44行代码主要是恢复现场和中断返回到用户态的代码。第42行中宏定义RESTORE_REGS的功能与宏定义SAVE_ALL恰好相反,用于将保存在内核栈的现场信息还原到相应的寄存器中,完成现场的恢复工作。第43行使用指令irq_return也就是iret进行中断的返回,该指令以当前栈中的元素按出栈顺序设置系统的%eip、%cs、%eflags、%esp、%ss寄存器。到此为止,系统完成了一次系统调用处理,返回用户态继续运。
下面给出该阶段的一个流程图:
这里写图片描述

总结

整个系统调用从产生0x80软中断开始,然后进入系统调用sys_call,在sys_call里系统保存现场,然后跳转到相应的系统内核函数里执行,执行完后然后通过寄存器eax返回用户态。系统调用是一种特殊的中断。

你可能感兴趣的文章
Android 属性系统设计分析
查看>>
OpenGL 3D 超级宝典学习笔记
查看>>
android2.3 --- Service Manager分析
查看>>
Android 权限控制代码分析
查看>>
SurfaceFlinger GraphicBuffer内存共享缓冲区机制
查看>>
android -- NDK 编译环境搭建
查看>>
学习中医之读书计划
查看>>
感冒冶法 --- 喝葱姜水
查看>>
委中穴 --- 腰疼好疗效
查看>>
《医间道》读后感<1>
查看>>
android-bluetooth移植碰到的问题
查看>>
android-jni与java参数传递
查看>>
android-jni与java参数传递(续集)
查看>>
android 常见死机问题--log分析
查看>>
android - IR 遥控器无效
查看>>
andorid - NDK 编译mips-platform代码
查看>>
Android系统广电运营之我感
查看>>
android - 编写强引用java对象
查看>>
Android ---- WebView与JavaScript交互调用(1)
查看>>
Android ---- WebView与JavaScript交互调用(2)
查看>>