分析system_call中断处理过程

北战南征 提交于 2020-03-02 04:46:37

最初版本的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有没有被加进去:1

可以看到,现在这两个命令应该都可以用了,我们来试一试fork-asm:2

可以看到,系统调用sys_fork()时就到了我们设置的断点,我们来继续单步调试:3

接下来我们分析一下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课程


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!