1. 程式人生 > >基於mykernel完成時間片輪詢多道進程的簡單內核

基於mykernel完成時間片輪詢多道進程的簡單內核

完成 null timer state 啟動進程 全局 led put clas

基於mykernel完成時間片輪詢多道進程的簡單內核

學號:282

原創作品轉載請註明出處 + https://github.com/mengning/linuxkernel/

一、實驗目的

基於mykernel,完成一個簡單的時間片輪轉多道程序內核代碼並分析。

二、實驗步驟

1. 環境搭建

環境為 Linux QEMU,實驗樓網站中直接打開即可食用。

  1. 在實驗樓終端中執行下列命令對內核進行打補丁操作

    cd LinuxKernel/linux-3.9.4
    rm mykernel -rf
    patch -p1 < ../mykernel_for_linux3.9.4sc.patch
  2. 編譯源碼

    make allnoconfig
    make
  3. 編譯成功後,使用QEMU工具來顯示代碼執行過程。內核成功啟動!

    技術分享圖片

2. 基於mykernel實現時間片輪詢多道進程

通過上面的步驟,我們將孟寧老師GitHub上面myinterrput.c和mymain.c 替換掉mykernel中的文件,將mypcb.h移動到相同文件夾下。

  1. git克隆源碼

    git clone https://github.com/mengning/mykernel.git

    技術分享圖片

  2. 替換原代碼

    技術分享圖片

  3. 重新編譯和運行

    技術分享圖片

3. 源碼分析

  • 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;
        // -1 unrunnable, 0 runnable, >0 stopped
        volatile long state;  
        // 進程堆棧
        unsigned long stack[KERNEL_STACK_SIZE];
        // CPU-specific state of this task 
        struct Thread thread;
        // 入口事件
        unsigned long task_entry;
        // 下一個pcb塊的位置
        struct PCB *next;
    }tPCB;
    
    void my_schedule(void);

    由代碼可知,mypcb.h主要通過結構體的方式定義了進程的基本信息:進程狀態、進程id以及進程的堆棧信息等,其余信息已在註釋中表明。

  • 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;   
    }

    由以上代碼可知,myinterrupt.c主要實現兩個函數:my_time_handler和my_schedule。

    • my_time_handler(), 每1000毫秒產生一個時鐘中斷,同時設置my_need_sched=1mymain.c中的函數就會調用my_schedule()以執行進程切換的操作。
    • my_schedule(),用於進程的切換操作。
      1. 聲明next,pre指針執行下一個需要調度的進程和上一個進程
      2. 判斷下一個需要調度的進程的狀態值,為0則切換
      3. 通過匯編實現進程的切換。保存現場,將當前線程相關內容入棧,上一線程相關內容出棧,然後切換線程ip;
  • 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);
            }     
        }
    }

    mymain.c中,主要是進程的初始化並負責啟動進程:

    1. 初始化一個進程 。為其分配進程編號、進程狀態state、進程堆棧、線程、任務實體等,並將其next指針指向自己。
    2. 初始化更多的進程 。根據第一個進程的部分資源,包括內存拷貝函數的運用,將0號進程的信息進行了復制,修改pid等信息。
    3. 設置當前進程 。因為是初始化,所以當前進程就決定給0號進程了,通過執行嵌入式匯編代碼,開始執行mykernel內核。
    4. my_process函數。每10000000次,打印當前進程的pid,全局變量my_need_sched,通過對my_need_sched進行判斷,若為1,則通知正在執行的進程執行調度程序,然後打印調度後的進程pid。

三、實驗總結

操作系統是指控制和管理整個計算機系統的硬件和軟件資源,並合理地組織調度計算機的工作和資源的分配,以提供給用戶和其他軟件方便的接口和環境的程序集合。
通過本次實驗:

  1. 為什麽要進行處理機調度?
    在多道程序系統中,進程的數量往往多於處理機的個數,進程爭用處理機的情況在所難免。處理機調度是對處理機進行分配,按照一定的算法選擇一個進程並將處理機分配給它運行,以實現進程並發執行。
  2. 什麽時時間片輪轉調度算法?
    系統將所有就緒進程按到達時間的先後次序排成一個隊列,進程調度總是選擇就緒隊列中的第一個進程執行,即先來先服務,但僅能運行一個時間片,如100ms。在使用完一個進程片後,即使進程還未完成,它也必須釋放處理機給下一個就緒的進程,然會回到就緒隊列的末尾重新排隊。
  3. 操作系統(內核)是如何工作的?
    • Linux是一個多進程的操作系統,所以,其他的進程必須等到正在運行的進程空閑CPU後才能運行。
    • 進程是動態執行的實體,內核是進程的管理者。進程不但包括程序的指令和數據,而且包括程序計數器和CPU的所有寄存器以及存儲臨時數據的進程堆棧。
    • 系統有一個進程控制表(process table),一個進程就是其中的一項。

基於mykernel完成時間片輪詢多道進程的簡單內核