最初版本的MenuOS只支持version和help命令,显然这并不能满足我们的需求。我们现在来为它添加一个fork命令和fork-asm命令,其作用是测试fork的系统调用。
要增加一个命令也并不难,只需要~/LinuxKernel/linux-3.18.6/menu/test.c里的main函数中添加下面一行,然后添加它的实现(需要定义在main函数前面)就可以了。
MenuConfig("fork","Test system call fork",Fork);
最后一个参数Fork是一个函数指针,也就是我们对它的定义:
int Fork(int argc, char *argv[])
{
pid_t fpid;
int count = 0;
fpid = fork();
printf("Now pid = %d\n", fpid);
if(fpid < 0)
printf("Error in fork!");
else if(fpid == 0){
printf("I am the child process, my process id is: %d\n", getpid());
count++;
}
else{
printf("I am the parent process, my process id is: %d\n", getpid());
count++;
}
printf("Now count = %d\n", count);
return 0;
}
同样的方法,也可以添加ForkAsm函数与命令(在我的上一篇博文《Linux下嵌入汇编代码调用API using fork()》中就有Fork()与ForkAsm()的实现,只要改下函数名就好了)。
现在,我们来通过gdb调试一下我们刚刚添加的命令从调用到运行结束的过程:
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
打开另外一个终端,启动gdb,我们来链接内核并添加断点:
(gdb) file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
(gdb) target remote:1234 # 建立gdb和gdbserver之间的连接,按c让qemu上的Linux继续运行
(gdb) break start_kernel # 断点的设置可以在target remote之前,也可以在之后
(gdb) break sys_fork # sys_fork是fork的系统调用入口
现在我们按c让内核启动,启动完成后我们在help里看一看fork和fork-asm有没有被加进去:
可以看到,现在这两个命令应该都可以用了,我们来试一试fork-asm:
可以看到,系统调用sys_fork()时就到了我们设置的断点,我们来继续单步调试:
接下来我们分析一下system_call的具体调用过程,详细代码见/kernel/entry_32.S
这段汇编代码较为复杂,还是来看一下简化版本的吧:
.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
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:
... ; deal with pending signals
END(work_pending)
画了一个流程图,见笑见笑~
总结:在系统调用结束返回(iret)之前,可能再次进行系统调度(call_schedule),调度过程中还可能发生进程上下文与中断上下文之间的切换。系统完成这一次调用后,会继续检查任务队列,之后才执行iret返回。
陈政/arc001 原创作品转载请注明出处 《Linux内核分析》MOOC课程
来源:oschina
链接:https://my.oschina.net/u/2431498/blog/647887