用gdb追踪系统mkdir执行系统调用的过程,并分析system_call的中断处理过程。
首先应当将有关mkdir代码载入menu中,重新装载menu:
之后打开test.c文件,在其中加入调用mkdir函数的定义。
执行make rootfs,从而打开menu镜像,我们发现mkdir就装载进menu的命令中了:
然后我们用gdb在sys_mkdir处设置断点。
如果继续进行单步调试的话,可能会进入汇编语言部分,此时就无法看到代码执行的细节了。所以说,用常规的gdb方法是不能对具体的内核代码进行调试的。如果想去对这部分进行调试,有兴趣的可以进一步查阅资料去了解一下。
system_call的代码还是比较复杂的,所幸老师在课上为我们对它的代码进行了简化,本博客将围绕这个代码来进行分析。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
.macro
INTERRUPT_RETURN iret .endm .macro
SAVE_ALL ... .endm .macro
RESTORE_INT_REGS ... .endm ENTRY(system_call) SAVE_ALL syscall_call: call
*sys_call_table(,%eax,4) movl
%eax,PT_EAX(%esp) syscall_exit: testl
$TIF_ALLOWORK_MASK,%ecx jne
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 END(syscall_exit_work) work_pending: testb
$_TIF_NEED_RESCHED,%cl jz
work_notifysig work_resched: call
schedule jz
restore_all work_notifysig: ... END(work_pending) |
代码从ENTRY(system_call)处正式进入程序。此时实际上已经触发了中断,首先通过SAVE_ALL将进程和中断上下文存储起来;之后,通过调用sys_call_table(,%eax,4)执行具体的系统调用(在这里是sys_mkdir),并在系统调用执行结束后将返回值存储到esp中。
之后,需要检测是否需要进入syscall_exit_work。如果不需要的话,就通过RESTORE_INIT_REGS将之前存储的上下文重新载入,并调用iret结束中断,返回之前的进程。
syscall_exit_work执行了一些进程调度、消息传递的工作,如果进行了进程切换,可能要继续触发新的中断,执行新的系统调用。
system_call的流程图简图如图所示(边上的圆框表示具体的执行代码):
总结:
本文对system_call的执行流程进行了初步的分析。通过阅读代码得知:在中断处理的过程中,需要保存之前程序的上下文,并执行相关的系统调用。此时若执行进程调度和切换,则可能触发新的中断,并保存当前的环境,如此层层堆叠,直到回到最初调用的程序中。