杨怡泽 原创作品转载请注明出处《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
实验
本周的实验是跟踪系统调用,所以使用上周的getuid函数。
在test.c内写入GetUid和GetUidAsm。
和MenuConfig
make并运行MenuOS,用gdb设置断点start_kernel
设置断点sys_getuid
并一步步运行
system_call过程分析
根据伪代码,画出如下流程图
接下来分析一下伪代码
.macro INTERRUPT_RETURN # 中断返回
iret
.endm
.macro SAVE_ALL # 保护现场
...
.macro RESTORE_INT_REGS
...
.endm
ENTRY(system_call)
SAVE_ALL # 保护现场
syscall_call: 正式开始执行
call *sys_call_table(,%eax,4) #系统调用对应的系统函数,根据系统调用号来查表里的位置
movl %eax, PT_EAX(%esp) # store the return value
syscall exit:
testl $_TIF_ALLWORK_MASK, %ecx # current->work
jne syscall_exit_work #如果当前系统有一些信号需要调用则跳转到syscall_exit_work
restore_all:
RESTORE_INT_REGS
irq_return:
INTERRUPT_RETURN # 执行完毕
ENDPROC(system_call)
syscall_exit_work:
testl $_TIF_WORK_SYSCALL_EXIT, %ecx
jz work_pending #跳转到work_pending:
END(syscall_exit_work)
work_pending:
testb $_TIF_NEED_RESCHED, %cl
jz work_notifysig #处理信号
work_resched: #重新调度
call schedule #调度完成后再call schedule
jz restore_all #在跳转到restore_all,返回系统调用
work_notifysig: #信号处理
... ; deal with pending signals
END(work_pending)
可以看到无论是中断返回(ret_from_intr) ,还是系统调用返回,都使用了 work_pending 和resume_userspace。
对于宏SAVE_ALL来说,这条语句会把将寄存器的值压入堆栈当中,压入堆栈的顺序对应struct pt_regs ,出栈时,这些值传递到struct pt_regs的成员,实现从汇编代码向C程序传递参数。struct pt_regs可以在arch/x86/include/asm/ptrace.h中查看。
总结
当用户态的进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。因为内核实现了很多不同的系统调用,因此进程必须传递一个名为系统调用号的参数来识别所需的系统调用。
系统调用处理程序与其他异常处理程序的结构类似,执行下列操作:
1、在内核态栈中保存大多数寄存器的内容
2、代用系统调用服务例程的对应C函数来处理系统调用
3、退出系统调用程序:用保存在内核栈中的值加载寄存器,CPU从内核态切换回用户态