上一次使用C函数和嵌入式汇编完成getpid系统调用,本次把上次写的系统调用代码加入MenuOS中,执行并跟踪这个系统调用的执行的过程。
这里将上次的代码再贴上来,
#include<stdio.h>
#include<unistd.h>
int main(){
pid_t pid,pidasm;
pid=getpid();
printf("use getpid(): current process pid is %d\n",pid);
asm volatile(
"mov $0,%%ebx\n\t"
"mov $0x14,%%eax\n\t"
"int $0x80\n\t"
"mov %%eax,%0\n\t"
:"=m"(pidasm)
);
printf("use asm : current process pid-asm is %d\n",pidasm);
}
在MenuOS中加入getpid系统调用
在test.c中加入自己的系统调用代码如下
编译运行后,选择getpid-asm选项,正确显示如下
系统调用代码分析
关键性代码如下, 这里是详细的代码 。
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
已经在代码中关键位置的作用做了注释。
当用户态进程发出int $0x80指令时,cpu切换到内核态并开始从地址system_call处开始执行指令。System_call()函数首先把系统调用号和这个异常处理程序可以用到的所有cpu寄存器保存到相应的内核栈中,通过一个宏指令SAVE_ALL实现的。
然后会调用sys_call_table,通过eax寄存器的值查找系统调用表,找到几号系统调用,然后调用相应的系统调用。
当系统调用完成时,内核会检测一些情况,比如进程的切换或者信号发生什么的。如果发生了这些情况,内核会转向syscall_exit_work中执行,在syscall_exit_work中先通过work_pending判断是信号处理还是进程调度。
最后一步是恢复现场,通过iret的方式,把之前压在栈中的寄存器的值全部弹出。到这里就表示完成了整个系统调度了。
总结
根据对系统调用流程的分析,画了一个system_call的处理流程图如下