用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的执行流程进行了初步的分析。通过阅读代码得知:在中断处理的过程中,需要保存之前程序的上下文,并执行相关的系统调用。此时若执行进程调度和切换,则可能触发新的中断,并保存当前的环境,如此层层堆叠,直到回到最初调用的程序中。