2019-2020-1 20199320《Linux内核原理与分析》第三周作业

第二章 操作系统是如何工作的

一、虚拟一个x86的CPU硬件平台

  1. 在实验楼环境下输入如下命令:

    > cd ~/LinuxKernel/linux-3.9.4
    > qemu -kernel arch/x86/boot/bzImage

    得到如图结果:

  2. QEMU窗口不显示输出结果,为方便理解内核启动效果,重新输入如下命令:

    > cd ~/LinuxKernel/linux-3.9.4
    > rm -rf mykernel
    > patch -p1 < ../mykernel_for_linux3.9.4sc.patch
    > make allnoconfig
    > qemu -kernel arch/x86/boot/bzImage

    经过make后,可在QEMU窗口清楚看到内核启动效果:

  3. 进入mykernel目录,可以看到QEMU输出内容的代码mymain.c和myinterrupt.c,如图:

    mymain.c的执行功能是每100000次输出一次“my_start_kernel here i”,同时有一个中断处理程序的上下文环境,周期性地产生时钟中断信号,能够触发myinterrupt.c中的代码(如下图),输出“>>>my_timer_hender here<<<<”。

二、在mykernel基础上构造一个简单的操作系统内核

  1. 增加一个 mypcb.h 头文件,用来定义进程控制块,即进程结构体的定义,具体代码请点击“mypcb.h”;

  2. mymain.c 进行修改,这里是mykernel内核代码的入口,负责初始化内核的各个组成部分。现对部分关键代码进行分析:

    • 启动第一个进程
     /* start process 0 by task[0] */
        pid = 0;
        my_current_task = &task[pid];
     asm volatile(
         "movl %1,%%esp\n\t"     /* 将进程原堆栈栈顶的地址(这里是初始化的值)存入ESP寄存器 */
         "pushl %1\n\t"          /* 将当前EBP寄存器值入栈 */
         "pushl %0\n\t"          /* 将当前进程的EIP(这里是初始化的值)入栈 */
         "ret\n\t"               /* ret命令正好可以让入栈的进程EIP保存到EIP寄存器中 */
         : 
         : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)   /* input c or d mean %ecx/%edx*/
     );
    } 

    具体分析如下图:

    附:这里用到vi编辑器的全删除,可输入可输入:1,$d从第一行删到末尾,其他删除操作点击这里访问。

  3. interrupt.c 进行修改,主要是增加了进程切换的代码,现对关键代码进行分析:

    • 进程0启动,开始执行my_process(void)函数的代码。
    if(next->state==0)   /*next->state==0对应进程next对应进程曾经执行过。*/
    {
        //进行进程调度关键代码。
        asm volatile(
            "pushl %%ebp\n\t"   /*保存当前EBP到堆栈中。*/
            "movl %%esp,%0\n\t" /*保存当前ESP到当前PCB中。*/
            "movl %2,%%esp\n\t" /*将next进程的堆栈栈顶的值存到ESP寄存器。*/
            "movl $1f,%1\n\t"   /*保存当前进程的EIP值,下次恢复进程后将在标号1开始执行。*/
            "pushl %3\n\t"      /*将next进程继续执行的代码位置(标号1)压栈。*/
            "ret\n\t"           /*出栈标号1到EIP寄存器。*/
            "1:\t"              /*标号1,即next进程开始执行的位置。*/
            "pop1 %%ebp\n\t"    /*恢复EBP寄存器的值。*/
            : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
            : "m" (next->thread.sp),"m" (next->thread.ip)
        );
        my_current_task=next;
        printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
    }
    else
    {
        next-state=0;
        my_current_task=next;
        printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
    /*转换到新的进程中*/
        asm volatile(
          "pushl %%ebp\n\t"   /*保存当前EBP到堆栈中。*/
          "movl %%esp,%0\n\t" /*保存当前ESP到当前PCB中。*/
          "movl %2,%%esp\n\t" /*载入next进程的栈顶地址到ESP寄存器。*/
          "movl %2,%%ebp\n\t" /*载入next进程的堆栈基地址到EBP寄存器*/
          "movl $1f,%1\n\t"   /*保存当前EIP寄存器值到PCB,这里$1f是指上面的标号1。*/
          "push %3\n\t"       /*把即将执行的进程的代码入口地址入栈。*/
          "ret\n\t"           /*出栈进程的代码入口地址到EIP寄存器。*/
          : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
          : "m" (next->thread.sp),"m" (next->thread.ip)
        );
    }

    三、总结

    本章主要借助Linux内核部分源代码模拟了计算机3法宝(存储程序计算机、函数调用堆栈、中断),通过认真对源代码中汇编部分的分析,基本了解了进程的切换,进程在执行过程中,如果有需要调度其他进程时,需先保存当前进程的上下文环境,当该进程被调度时需先恢复上下文环境,以此实现了进程的并发执行,大大提高了计算机运行效率。

猜你喜欢

转载自www.cnblogs.com/liangxu111/p/11604983.html
今日推荐