赵建清+原创作品转载请注明出处+《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的代码:
-
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_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的定义也在相同的文件中,用于设置堆栈指针寄存器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) |