说明
欧长坤 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 这学期学校恰好有操作系统的课程,上个学习就开始寻思研究研究Linux内核代码,恰好MOOC有这个课程,遂选了此课。
一、准备工作
本周的实验是第四周实验的自然延伸。同样的,它也并不难。 我们可以在http://codelab.shiyanlou.com/xref/linux-3.18.6/arch/x86/syscalls/syscall_32.tbl中查看系统调用号。
如果你上周没有看过我的文章,你可以看看这里。我使用了systeminfo这样一个系统调用。
二、实验过程:跟踪系统调用
我们使用了sysinfo
这个库函数API。
下面的三张实验图显示了我们已经成功的使用gdb调试跟踪到了sysinfo这个系统调用(函数名为sys_sysinfo
),但是随后使用next命令继续调试时,显然无法再继续调试了,因为不能直接使用gdb来对使用汇编代码编写的system_call
进行调试和追踪。这里有一篇文章介绍了如何使用gdb来调试汇编代码,我就不继续展开了:http://www.doc88.com/p-0781911176267.html
三、system_call过程分析
不说废话,老师上课对system_call的分析过程非常清楚,伪代码抓住了重点,我们很容易就能够画出下面的流程图:
接下来我们逐行分析系统调用处理过程的汇编伪代码
|
|
可以看到无论是中断返回(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
中查看。
而接下来为何直接就跳到了sys_call_talbe
呢,这里伪代码里面没有说明清楚情况,我们来看真实代码片段:
|
|
首先,GET_THREAD_INFO
宏可以获得当前进程的thread_info
结构的地址,获取当前进程的信息。而jnz syscall_trace_entry
比较testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
的结果不为零的时候跳转。如此可对用户态进程传递过来的系统调用号的合法性进行检查。如果不合法则跳转到syscall_badsys
标记的命令处。
所以,比较结果大于或者等于最大的系统调用号的时候跳转,合法则跳转到相应系统调用号所对应的服务例程当中,也就是在sys_call_table
表中找到了相应的函数入口点。由于sys_call_table
表的表项占4字节,因此获得服务例程指针的具体方法是将由eax保存的系统调用号乘以4再与sys_call_table
表的基址相加。 然后,进入到系统调用表查找到系统调用服务程序的入口函数的地址,再进行跳转。
这样便完成了对sys_call_table
的进入。
接下来伪代码马上就来到了syscall_exit
,真实代码情况也是如此,在执行完syscall_call
后,已经没有比较在继续处理了,因此马上接触并退出系统调用。
但是这个退出的过程就变得非常的复杂了。
从syscall_exit
开始到irq_return
的真实代码:
|
|
这段处理过程就涉及到内核中断返回时候涉及到得一些非常繁杂的细节了,比如work_pending
等其它细节工作,我们想要彻底弄清楚,在这个篇幅里显然是不够的,因此我也不再继续赘述。值得注意的事情是work_pending
后回涉及到处理进程信号量的问题,这里涉及到了很多进程调度的内容,我们在以后的关于进程部分的课程作业中再详细谈及它吧。
完成这些其余的工作之后,最终来到了irq_return
,在这里使用宏 INTERRUPT_RETURN
实际上就是iret指令,恢复现场,最终完成了系统调用中断处理的返回。
四、总结
好了,我们来总结一下:
1、用户态到内核态需要int 0x80进行中断,只有生成了中断向量后才可以切换状态;
2、中断处理让CPU停止当前工作转为执行系统内核中预设的一些任务,因此必须要对当前CPU执行的任务进行执行现场的保护工作,并对一些其他杂七杂八的工作进行检查,完成调用后,再进行检查,才能执行iret返回。
3、系统内部调用涉及CPU架构等内容,不同的CPU对于系统调用的汇编具体代码是不一样的。