Chinaunix首页 | 论坛 | 博客
  • 博客访问: 142197
  • 博文数量: 57
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 365
  • 用 户 组: 普通用户
  • 注册时间: 2015-04-14 23:29
文章分类

全部博文(57)

文章存档

2018年(3)

2017年(2)

2016年(42)

2015年(10)

我的朋友

分类: LINUX

2015-04-15 09:34:23

赵建清+原创作品转载请注明出处+《Linux内核分析》MOOC课程

概述

在Linux系统中应用程序发起系统调用后,使用int $0x80或sysenter汇编指令将CPU切换到内核态,然后开始从地址system_call处执行命令。

代码分析

IA-32体系结构下system_call的定义位于内核代码arch/x86/kernel/entry_32.S文件中,由文件名即可知道system_call为汇编代码。下面附上linux-3.18.6内核版本中system_call的代码:
  1. ENTRY(system_call)
  2.     RING0_INT_FRAME                       # can't unwind into user space anyway
  3.     ASM_CLAC
  4.     pushl_cfi %eax                             # save orig_eax
  5.     SAVE_ALL
  6.     GET_THREAD_INFO(%ebp)
  7.                                                         # system call tracing in operation / emulation
  8.     testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
  9.     jnz syscall_trace_entry
  10.     cmpl $(NR_syscalls), %eax
  11.     jae syscall_badsys
  12. syscall_call:
  13.     call *sys_call_table(,%eax,4)
  14. syscall_after_call:
  15.     movl %eax,PT_EAX(%esp)              # store the return value
  16. syscall_exit:
  17.     LOCKDEP_SYS_EXIT
  18.     DISABLE_INTERRUPTS(CLBR_ANY)   # make sure we don't miss an interrupt
  19.                                                          # setting need_resched or sigpending
  20.                                                          # between sampling and the iret
  21.     TRACE_IRQS_OFF
  22.     movl TI_flags(%ebp), %ecx
  23.     testl $_TIF_ALLWORK_MASK, %ecx  # current->work
  24.     jne syscall_exit_work

  25. restore_all:
  26.     TRACE_IRQS_IRET
  27. restore_all_notrace:
  28. #ifdef CONFIG_X86_ESPFIX32
  29.     movl PT_EFLAGS(%esp), %eax         # mix EFLAGS, SS and CS
  30.     # Warning: PT_OLDSS(%esp) contains the wrong/random values if we
  31.     # are returning to the kernel.
  32.     # See comments in process.c:copy_thread() for details.
  33.     movb PT_OLDSS(%esp), %ah
  34.     movb PT_CS(%esp), %al
  35.     andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax
  36.     cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax
  37.     CFI_REMEMBER_STATE
  38.     je ldt_ss                                           # returning to user-space with LDT SS
  39. #endif
  40. restore_nocheck:
  41.     RESTORE_REGS 4                              # skip orig_eax/error_code
  42. irq_return:
  43.     INTERRUPT_RETURN
RING0_INT_FRAME的定义也在相同的文件中,用于设置堆栈指针寄存器esp和指令指针寄存器eip,使其指向内核:
.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
第4行将通用寄存器eax入栈,用于保存系统调用号。
第5行宏SAVE_ALL在栈中保存中断处理程序可能会使用到的所有CPU寄存器,但不包括eflags、cs、eip、ss和esp:
.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
第6行ebp用于存放当前进程thread_info结构的地址。
第8行检查是否有某一调试程序正在跟踪执行程序对系统调用的调用,如果需要追踪当前执行程序,则调转到syscall_trace_entry,调试进程会收集必要的信息。
第9行对用户态进程传来的系统调用号进行有效性检查。如果这个系统调用号不在有效范围内,系统调用处理程序就终止,并在eax中设置错误返回码。
第12行调用与eax中所包含的系统调用号对应的特定服务例程,因为分派表中的每个表项占4个字节,因此首先把系统调用号乘以4,再加上sys_call_table分派表的起始地址,内核就找到了要调用的服务例程。
第18行和第21行关闭本地中断并检查当前进程的thread_info结构中的标志。如果所有的标志没有被设置则跳转到restore_all标记处。
第29行到第35行用于恢复保存在内核栈中的寄存器的值。
第44行执行iret汇编指令重新开始执行用户态进程。

总结

system_call是发起系统调用的必经之路,在执行之前CPU处于用户态,执行system_call过程中系统进入内核态,执行系统调用服务例程,system_call
返回后又切换回用户态。

阅读(1232) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~