1. 程式人生 > >linux kernel 進程切換原理分析

linux kernel 進程切換原理分析

ext 順序執行 圖片 ima 兩個 vol 行操作 ecif 簡單

前言:本文為我學習孟寧老師的《庖丁解牛linux 內核》課程的簡單總結,同時作為課間作業。

唐建,《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000

1、概述

本文通過一個簡單的自定義內核程序來簡單描述內核中程序的相互切換過程,給大家展示內存多進程隨著時間片輪轉而來回切換

的過程以及原理。

2、代碼結構

我們的例子代碼結構:主要有兩個文件mypcb.c myinterrupt.c

 mypcb.c文件內容:

技術分享圖片

技術分享圖片

myinteruppt.c文件內容:

技術分享圖片

技術分享圖片

my_start_kernel() ,系統的起點,完成系統環境的準備

my_process()這問進程的主體,我們將起多個進程實例。

my_schedule() 系統進程調度器,我們將在這個函數看到進程是怎麽切換的。

my_time_handler() 時間中斷函數,這個函數裏面產生時間片,時間片到了後就需要進行進程切換了。

3、my_start_kernel() 系統環境的準備。

我們先看下重要的結構體 ,進程的結構體,相當於系統的task_struct

 

struct Thread {
unsigned long ip;
unsigned long sp;
};

typedef struct PCB{
int pid; ——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; ——線程,不用理會線程。主要看其內容ip=eip,sp=esp。就是進程當前執行到哪裏了。
unsigned long task_entry; ——進程的入口
struct PCB *next; ——下一個進程
}tPCB;

我們的linux 系統進程調度大體上就是操作task_struct,所以我們這模擬的系統就是圍繞這tpcb進行操作

回到正題,my_start_kernel如下圖這個函數分三部分

(1)、0號進程的初始化

進程的入口為my_process,所以我們一會主要看怎麽跳轉到進程的入口的。

這裏task_entry 賦予了進程入口my_process()。同時sp=esp = 進程的棧底,因為是剛開始嘛,棧還是空的。

(2)、fork多個進程,這裏模擬了fork的過程,

技術分享圖片

(3)、我們這著重講這個,就是my_start_kernel怎麽切換到0號進程的。

  asm volatile(
      "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */ ——將esp 賦值為0號進程的sp,好了,這樣棧就切換到0號進程的棧了
      "pushl %1\n\t" /* push ebp */ ——將將因為0號進程還沒開始跑呢,所以ebp=esp啊,所以這裏實際上是將ebp壓棧
      "pushl %0\n\t" /* push task[pid].thread.ip */ ——將ip=eip 將0號進程的eip入棧。
      "ret\n\t" /* pop task[pid].thread.ip to eip */ ——ret= pop eip。準備切換到0號進程了,將0號進程的ip放入eip寄存器中。
      "popl %%ebp\n\t" ——sp=ebp,準備棧底。到這裏,ebp、eip、esp都準備好了,註意eip=my_process,

也就是已經切換到0號進程了。
      :
      : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/
  );

4、時間片

  技術分享圖片

如上圖,實際上就是沒1000個時鐘就賦值my_need_sched=1,這個是調度標誌位,表示要開始調度到下一個進程了。

5、my_process

前面我們講到0號進程已經開始跑起來了,也就是跑到了my_process這裏。

技術分享圖片

如上圖,可以看到這裏是死循環,當發現調度標誌被設置為1時,表示要開始調度了,這個進程不能再執行了,輪到別人了,於是調用my_schedule去切換到其他進程。

6、my_schedule,進程切換。

現在來到我們的核心了,實際上就保存上一個進程的現場,然後將需要調度的進程的現場恢復到寄存器中,讓寄存器按照這個進程的現場運行。

技術分享圖片

(1)、這個就是取出馬上需要進程的結構體

(2)、這個就是難點了

  /* switch to next process */
  asm volatile(
    "pushl %%ebp\n\t" /* save ebp */ ——將本進程的ebp入棧
    "movl %%esp,%0\n\t" /* save esp */ ——將esp保存到本進程中,用於下次執行
    "movl %2,%%esp\n\t" /* restore esp */ ——將新進程的sp寫入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)
  );

這個實際上就是保存本進程的信息,然後將下一個進程的東西裝入寄存器,並開始執行。

7、執行結果

可以推測,我們的進程是按照序號順序執行的

技術分享圖片

linux kernel 進程切換原理分析