Linux从用户层到内核层系列 - TCP/IP协议栈部分系列11: 再话Linux系统调用

来源:互联网 发布:网络原创女歌手郑靖雯 编辑:程序博客网 时间:2024/06/07 17:29

题记:本系列文章的目的是抛开书本从源代码和使用的角度分析Linux内核和相关源代码,byhankswang和你一起玩转linux开发

轻松搞定TCP/IP协议栈,原创文章欢迎交流, byhankswang@gmail.com微笑

欢迎加入到CHLK - Linux开发交流群 QQ:327084515 讨论Linux开发相关问题

 

再话Linux系统调用

 

本文是系列博文《Linux从用户层到内核层系列 - TCP/IP协议栈部分系列6:linux 系统调用中断向量表》的姊妹篇,在那篇博文中列举出了X86体系结构32位和64为的中断向量号,本文讲解如何从用户层系统调用进入到该向量表中,并分析在此过程中的用户进程和内核进程的切换,以及在内核执行完系统调用后是如何返回的。

Linux从用户层到内核层系列 - TCP/IP协议栈部分系列6:linux 系统调用中断向量表》URL:http://blog.csdn.net/byhankswang/article/details/9284023



对用户程序调用系统API的时候,会发生软中断0X80, 在软中断0X80之后做的工作我们开始分析:

说明:出于对他人知识分享的尊重,需要指明本文中关于寄存器的使用参考了司徒彦南先生于2002年4月8日的文档《简明X86汇编语言教程》


//within file linux-3.9.3/arch/x86/kernel/entry-32.S

ENTRY(system_call)                        //已由用户态陷入到内核态中
        RING0_INT_FRAME                 
        ASM_CLAC
        pushl_cfi %eax   

        //非常核心!保存相关寄存器的值,将来如何恢复之前进程的数据全部由这些寄存器来决定               
        SAVE_ALL                        

       // 通常ebp寄存器被高级语言编译器用以建造‘堆栈帧’来保存函数或过程的局部变量

       //此时可以知道将来返回到那个函数的那个部位
        GET_THREAD_INFO(%ebp)               
        testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
        jnz syscall_trace_entry
        cmpl $(NR_syscalls), %eax
        jae syscall_badsys

syscall_call:      //真正的系统调用!sys_call_table就是之前文章中列出的系统调用表的数组              
        call *sys_call_table(,%eax,4)
        movl %eax,PT_EAX(%esp)   //保存系统调用的返回值
syscall_exit:
        LOCKDEP_SYS_EXIT
        DISABLE_INTERRUPTS(CLBR_ANY)  //屏蔽其他系统调用
        TRACE_IRQS_OFF

        //寄存器ecx是通用寄存器,在保护模式中,可以作为内存偏移指针

        //(此时,DS作为 寄存器或段选择器),此时为返回到系统调用之前做准备
        movl TI_flags(%ebp), %ecx    
        testl $_TIF_ALLWORK_MASK, %ecx   //TEST 测试.(两操作数作与运算,仅修改标志位,不回送结果).
        jne syscall_exit_work
restore_all:
        TRACE_IRQS_IRET
restore_all_notrace:
        movl PT_EFLAGS(%esp), %eax      
        # 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                       //返回到用户空间,系统调用返回
restore_nocheck:
        RESTORE_REGS 4                  //忽略orig_eax和error_code
irq_return:
        INTERRUPT_RETURN


其中比较重要的SAVE_ALL到底保存了哪些重要的寄存器,我们列出代码来欣赏一下:

//within file linux-3.9.3/arch/x86/kernel/entry_32.S
.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



已经比较明了的知道了,用户进程发生系统中断之后内核所做的工作了,并且也知道了系统调用中断之后是如何保存的返回值,并正确的返回到用户空间。 Happy coding!