陈民禾——原创作品转载请注明出处—— 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

一.上周内容回顾

       计算机科学中有一句话,任何计算机相关问题都可以通过加一个中间层来解决。操作系统的系统调用是这样,system_call将api和系统函数连接起来,这样可以保证内核的安全,不会因为用户的失误操作而造成问题。操作系统为了安全,把一些重要的调用放在内核部分,这样只能通过触发系统调用来完成相应功能,这样可以保证内核的安全。

二.系统调用在内核代码中的机制和初始化

       这部分代码对我们来说理解整个操作系统的运行机制是非常重要的。因为杂过程中涉及到了进程调度的时机,系统调用的时机对我们理解操作系统的运行机制非常的重要,所以我们需要仔细的来分析一下system_call这个系统调用的过程。

   

      在xyz系统调用里面我们使用了int 0x80,在int 0x80中断向量对应着system_call,它是什么时候绑定起来的呢,是初始化的时候,初始化这个中断向量的时间,然后呢system_call的处理过程非常重要,这段汇编代码我们需要仔细的分析一下,在这个system_call调用的时候,哪里用到了sys_xyz,通过系统调用,通过中断向量来匹配起来的。那么在返回到用户态之前有一个返回时机,在我们调试的时候,你一直按n的话,从systime退出之后,下面会跟踪到schedule,我们先分析一下初始化,它从start_kernel里面有一个trap_init,trap_init里面有一个set_system_trap_gate(SYACALL_VECTOR(也就是系统调用的中断向量和system_call汇编代码的入口,这样一旦执行int 0x80,CPU 就自动跳转到system_call,这个位置来执行。

        在xyz系统调用里面我们使用了int 0x80,在int 0x80中断向量对应着system_call,它是什么时候绑定起来的呢,是初始化的时候,初始化这个中断向量的时间,然后呢system_call的处理过程非常重要,这段汇编代码我们需要仔细的分析一下,在这个system_call调用的时候,哪里用到了sys_xyz,通过系统调用,通过中断向量来匹配起来的。那么在返回到用户态之前有一个返回时机,在我们调试的时候,你一直按n的话,从systime退出之后,下面会跟踪到schedule,我们先分析一下初始化,它从start_kernel里面有一个trap_init,trap_init里面有一个set_system_trap_gate(SYACALL_VECTOR(也就是系统调用的中断向量和system_call汇编代码的入口,这样一旦执行int 0x80,CPU 就自动跳转到system_call,这个位置来执行。

 三.了解系统调用的基本代码

       这个代码实际上就是中断系统调用的处理过程,系统调用其实就是一个特殊一点的中断,存在保护现场和恢复现场的问题(比如save_all)同时我们要是仔细读这个代码的话,这个代码还相对比较复杂一点,这里有一个syscall_table(%eax,4)它是由系统调用的定义系统调用表,eax传递系统调用号,实际上,我们在这一条的时候在例子里面就是调用systime。syscall_after_all这个时候它就需要保存它的返回值,它在退出之前有一个syscall_exit_work,会检查当前进程是否需要执行 syscall_exit_work,如果需要处理,就会查看是否有进程信号或进程调度,若有进程信号或进程调度就再进行相应操作。

ENTRY(system_call)
    RING0_INT_FRAME        # can't unwind into user space anyway
    ASM_CLAC
    pushl_cfi %eax            # save orig_eax
    SAVE_ALL      #保存现场
    GET_THREAD_INFO(%ebp)    # system call tracing in operation / emulation
    testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
    jnz syscall_trace_entry
    cmpl $(NR_syscalls), %eax
    jae syscall_badsys

syscall_call:
    call *sys_call_table(,%eax,4)    #系统调用

syscall_after_call:
    movl %eax,PT_EAX(%esp)        # store the return value

syscall_exit:
    LOCKDEP_SYS_EXIT
    DISABLE_INTERRUPTS(CLBR_ANY)    # make sure we don't miss an interrupt
                                    # setting need_resched or sigpending
                                    # between sampling and the iret
    TRACE_IRQS_OFF
    movl TI_flags(%ebp), %ecx
    testl $_TIF_ALLWORK_MASK, %ecx    # current->work
    jne syscall_exit_work

restore_all:
    TRACE_IRQS_IRET    #恢复现场

irq_return:
    INTERRUPT_RETURN    #中断返回

 四.中断执行过程的流程图
    

五.我的实验过程及截图

       本次实验选用 Linux 操作系统 2 号系统调用,fork 函数,通过对比嵌入式汇编编程和系统函数编程,深入理解和分析系统调用的过程。

       命令行如下:

cd LinuxKernel
rm menu -rf
git clone https://github.com/mengning/menu.git
cd menu
make rootfs

      先打开实验楼里面的LinuxKernel,然后我们输入删除的操作,rm menu -rf,然后我们重新克隆一个,因为新版本的Menu已经把上次的两个系统调用,然后我们可以重新克隆一个,因为新版本的Menu已经提交到github里面。

       这个命令就是git clone https://github.com/mening/menu.git,进入到Menu,再ls一下,我们编译menu的过程可能比较繁琐,我们就使用脚本,我们直接输入make rootfs它就可以帮我们直接编译生成这个根文件系统,同时还自动帮我们启动起来这个我们按一个回车 没有任何信息就不输出,我们发现现在的命令也多了一些,我们还加了一些命令:
fork。 修改的函数代码如下所示:   
int menufork(int argc, char *argv[])
{
    pid_t fpid;
    int count = 0;
    fpid = fork();
    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("count: %d\n",count);
    return 0;

}

int menuforkAsm(int argc, char *argv[])
{
    pid_t fpid;
    int count = 0;
    asm volatile (
            "mov $0, %%ebx\n\t"
            "mov $0x2, %%eax\n\t"
            "int $0x80\n\t"
            "mov %%eax, %0\n\t"
            : "=m" (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("count: %d\n",count);
    return 0;

}
int main()
{
    PrintMenuOS();
    SetPrompt("MenuOS>>");
    MenuConfig("version","MenuOS V1.0(Based on Linux 3.18.6)",NULL);
    MenuConfig("quit","Quit from MenuOS",Quit);
    MenuConfig("time","Show System Time",Time);
    MenuConfig("time-asm","Show System Time(asm)",TimeAsm);

    /*  add for menufork   */
    MenuConfig("menufork","menufork --fork a proc", menufork);
    MenuConfig("menufork-asm","menufork-asm --fork a proc(asm)",menuforkAsm);


    ExecuteMenu();
}

使用gdb进行调试:
gdb
file linux-3.18.6/vmlinux    加载调试用的符号表
target remote:1234
b start_kernel  设置断点
c
b sys_fork

 

      我们使用命令vi test.c,test.c这里面从main 函数开始读,发现我们这个里面其实只增加了两行代码:这两行对应的有两个函数,一个menuforkAsm还有menufork函数:只是我们需要增加这些东西就可以完成对MenuOS的修改,只是我们需要增加新的命令的话,需要按照下面的方式,MenuConfig增加,然后对应的函数增加。
      给MenuOS增加menufork和menuforkAsm命令
      1.更新Menu代码到最新版本
      2.在main函数中增加MenuConfig
      3.增加对应的menufork函数和menuforkAsm函数
      4.make roootfs
六.总结
      本实验通过分析 Linux 内核系统调用源码,深入剖析了系统调用的全过程。通过 int 0x80 中断跳转到系统调用处理程序 system_call 函数处,执行相应的例程。但是,由于是代表的是用户进程,所以这个执行过程并不属于中断上下文,而是进程上下文。因此,系统调用执行过程中,可以访问用户进程的许多信息,也可以被更高优先级的进程抢占。    
       当系统调用完成后,把控制权交回到发起调用的用户进程前,内核又会有一次调度。如果发现有优先级更高的进程或当前进程的时间片用完,那么会选择优先级更高的进程或重新选择进程执行。
posted on 2016-03-27 17:59  20135124  阅读(840)  评论(0编辑  收藏  举报