Linux操作系统分析实验------linux kernel3

Linux操作系统分析实验------linux kernel3.9.4环境部署及基于mykernel的时间片轮转多道程序实现与分析

学号:508。原创作品转载请注明出处,中国科学技术大学孟宁老师的Linux操作系统分析 https://github.com/mengning/linuxkernel/

一、环境搭建

  1. 虚拟机:VMware® Workstation 14 Pro-14.1.3 build-9474260
    操作系统:ubuntu-18.04.2-desktop-amd64.iso
    搭建过程中遇到的问题,都在第7点中统一解释。

  2. 创建该作业的文件夹LinuxHW
    在这里插入图片描述

  3. 下载linux kernel3.9.4和孟宁老师的补丁mykernel_for_linux3.9.4sc.patch

    wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.9.4.tar.xz
    wget https://raw.github.com/mengning/mykernel/master/mykernel_for_linux3.9.4sc.patch
    

    并用tar xvf linux-3.9.4.jar进行解压缩得到linux-3.9.4文件。
    在这里插入图片描述

    在这里插入图片描述

  4. 开始打补丁包:先进入解压缩后的文件,在利用下面的指令打补丁:

    saltwind@ubuntu:~/LinuxHW$ cd linux-3.9.4
    saltwind@ubuntu:~/LinuxHW/linux-3.9.4$ patch -p1 < ../mykernel_for_linux3.9.4sc.patch
    

    在这里插入图片描述

  5. 进行编译:

    • 先复位:make allnoconfig
      在这里插入图片描述
    • 再编译:make
      在这里插入图片描述
  6. 安装qumu:sudo apt-get install qemu,安装完成后,就可以测试内核是否能够正确运行了:qemu -kernel arch/x86/boot/bzImage

    在这里插入图片描述

  7. 搭建过程问题总结:

    • 没有gcc:

      sudo apt-get updat
      sudo apt-get install gcc
      //若上两步不能正确安装,则再进行下面两步
      sudo apt-get update --fix-missing
      sudo apt-get install gcc
      
    • make编译时报错,fatal error: linux/compiler-gcc7.h: No such file or directory

      cd inculde/linux
      cp compiler-gcc4.h compiler-gcc7.h
      
    • 安装qemu出错,同上面gcc处理方式一样。

二、实验操作

这里主要就是实现进程间的时间片轮转。

  1. 我们利用孟宁老师提供的源码:将mykernel-master重命名为mykernel,并替换掉linux-3.9.4里面的mykernel
    在这里插入图片描述

  2. 然后重新进行编译运行:

    cd ..
    make allnoconfig
    make
    qemu -kernel arch/x86/boot/bzImage
    

    然后看到如下运行结果,这里已经从process 1跳转到了precess 2:

    在这里插入图片描述

  3. 下面进行源代码的分析,这里主要涉及三个文件:mypcb.h、mymain.c、myinterrupt.c

    • mypcb.h,先上代码

      /*
       *  linux/mykernel/mypcb.h
       *
       *  Kernel internal PCB types
       *
       *  Copyright (C) 2013  Mengning
       *
       */
      
      #define MAX_TASK_NUM        4
      #define KERNEL_STACK_SIZE   (unsigned long)1024*2  
      /* CPU-specific state of this task */
      struct Thread {
          unsigned long		ip;
          unsigned long		sp;
      };
      
      typedef struct PCB{
          int pid;
          volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
          unsigned long stack[KERNEL_STACK_SIZE];
          /* CPU-specific state of this task */
          struct Thread thread;
          unsigned long	task_entry;
          struct PCB *next;
      }tPCB;
      
      void my_schedule(void);
      

      在该文件中,有Thread结构体,主要用于存储当前进程中正在执行的线程的IP和SP。PCB结构体中各个字段含义:

      • pid:进程号
      • state:进程状态,在模拟系统中,所有进程控制块信息都会被创建出来,其初始化值就是-1,如果被调度运行起来,其值就会变成0
      • stack:进程使用的堆栈
      • thread:当前正在执行的线程信息
      • task_entry:进程入口函数
      • next:指向下一个PCB,模拟系统中所有的PCB是以链表的形式组织起来的
    • mymain.c文件

      /*
       *  linux/mykernel/mymain.c
       *
       *  Kernel internal my_start_kernel
       *
       *  Copyright (C) 2013  Mengning
       *
       */
      #include <linux/types.h>
      #include <linux/string.h>
      #include <linux/ctype.h>
      #include <linux/tty.h>
      #include <linux/vmalloc.h>
      
      
      #include "mypcb.h"
      
      tPCB task[MAX_TASK_NUM];
      tPCB * my_current_task = NULL;
      volatile int my_need_sched = 0;
      
      void my_process(void);
      
      
      void __init my_start_kernel(void)
      {
          int pid = 0;
          int i;
          /* Initialize process 0*/
          task[pid].pid = pid;
          task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */
          task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
          task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
          task[pid].next = &task[pid];
          /*fork more process */
          for(i=1;i<MAX_TASK_NUM;i++)
          {
              memcpy(&task[i],&task[0],sizeof(tPCB));
              task[i].pid = i;
      	//*(&task[i].stack[KERNEL_STACK_SIZE-1] - 1) = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];
      	task[i].thread.sp = (unsigned long)(&task[i].stack[KERNEL_STACK_SIZE-1]);
              task[i].next = task[i-1].next;
              task[i-1].next = &task[i];
          }
          /* start process 0 by task[0] */
          pid = 0;
          my_current_task = &task[pid];
      	asm volatile(
          	"movl %1,%%esp\n\t" 	/* set task[pid].thread.sp to esp */
          	"pushl %1\n\t" 	        /* push ebp */
          	"pushl %0\n\t" 	        /* push task[pid].thread.ip */
          	"ret\n\t" 	            /* pop task[pid].thread.ip to eip */
          	: 
          	: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)	/* input c or d mean %ecx/%edx*/
      	);
      } 
      
      int i = 0;
      
      void my_process(void)
      {    
          while(1)
          {
              i++;
              if(i%10000000 == 0)
              {
                  printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);
                  if(my_need_sched == 1)
                  {
                      my_need_sched = 0;
              	    my_schedule();
              	}
              	printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
              }     
          }
      }
      
      

      正如前文所述,这里的函数 my_start_kernel 是系统启动后,最先调用的函数,在这个函数里完成了0号进程的初始化和启动,并创建了其它的进程PCB,以方便后面的调度。在模拟系统里,每个进程的函数代码都是一样的,即 my_process 函数,my_process 在执行的时候,会打印出当前进程的 id,从而使得我们能够看到当前哪个进程正在执行。
      另外,在 my_process 也会检查一个全局标志变量 my_need_sched,一旦发现其值为 1 ,就调用 my_schedule 完成进程的调度

    • myinterrupt.c

      /*
       *  linux/mykernel/myinterrupt.c
       *
       *  Kernel internal my_timer_handler
       *
       *  Copyright (C) 2013  Mengning
       *
       */
      #include <linux/types.h>
      #include <linux/string.h>
      #include <linux/ctype.h>
      #include <linux/tty.h>
      #include <linux/vmalloc.h>
      
      #include "mypcb.h"
      
      extern tPCB task[MAX_TASK_NUM];
      extern tPCB * my_current_task;
      extern volatile int my_need_sched;
      volatile int time_count = 0;
      
      /*
       * Called by timer interrupt.
       * it runs in the name of current running process,
       * so it use kernel stack of current running process
       */
      void my_timer_handler(void)
      {
      #if 1
          if(time_count%1000 == 0 && my_need_sched != 1)
          {
              printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
              my_need_sched = 1;
          } 
          time_count ++ ;  
      #endif
          return;  	
      }
      
      void my_schedule(void)
      {
          tPCB * next;
          tPCB * prev;
      
          if(my_current_task == NULL 
              || my_current_task->next == NULL)
          {
          	return;
          }
          printk(KERN_NOTICE ">>>my_schedule<<<\n");
          /* schedule */
          next = my_current_task->next;
          prev = my_current_task;
          if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
          {        
          	my_current_task = next; 
          	printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);  
          	/* switch to next process */
          	asm volatile(	
              	"pushl %%ebp\n\t" 	    /* save ebp */
              	"movl %%esp,%0\n\t" 	/* save esp */
              	"movl %2,%%esp\n\t"     /* restore  esp */
              	"movl $1f,%1\n\t"       /* save eip */	
              	"pushl %3\n\t" 
              	"ret\n\t" 	            /* restore  eip */
              	"1:\t"                  /* next process start here */
              	"popl %%ebp\n\t"
              	: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
              	: "m" (next->thread.sp),"m" (next->thread.ip)
          	); 
          }  
          return;	
      }
      

      这里 my_timer_handler 函数会被内核周期性的调用,每调用1000次,就去将全局变量my_need_sched的值修改为1,通知正在执行的进程执行调度程序my_schedule。在my_schedule函数中,完成进程的切换。进程的切换分两种情况,一种情况是下一个进程没有被调度过,另外一种情况是下一个进程被调度过,可以通过下一个进程的state知道其状态。进程切换依然是通过内联汇编代码实现,无非是保存旧进程的eip和堆栈,将新进程的eip和堆栈的值存入对应的寄存器中,详见代码中的注释。

三、总结

在孟宁老师的指导下,进行本次实验后,我学习到操作系统的kernel主要就是中断机制和进程调度,通过软硬件的相互配合实现多任务处理。

猜你喜欢

转载自blog.csdn.net/Saltwind/article/details/88408732