最第一版本的MenuOS只支持version和help命令,显然这并不能知足咱们的需求。咱们如今来为它添加一个fork命令和fork-asm命令,其做用是测试fork的系统调用。linux
要增长一个命令也并不难,只须要~/LinuxKernel/linux-3.18.6/menu/test.c里的main函数中添加下面一行,而后添加它的实现(须要定义在main函数前面)就能够了。shell
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调试一下咱们刚刚添加的命令从调用到运行结束的过程:spa
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:rest
能够看到,系统调用sys_fork()时就到了咱们设置的断点,咱们来继续单步调试:code
接下来咱们分析一下system_call的具体调用过程,详细代码见/kernel/entry_32.Sorm
这段汇编代码较为复杂,仍是来看一下简化版本的吧:
.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课程