第二週:一個簡單的時間片輪轉多道程序內核代碼及分析

呂鬆鴻+ 原創做品轉載請註明出處 + 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000linux

1、函數調用堆棧

1. 計算機工做的三個法寶

  • 存儲程序計算機工做模型,計算機系統最最基礎性的邏輯結構;shell

  • 函數調用堆棧,高級語言得以運行的基礎,只有機器語言和彙編語言的時候堆棧機制對於計算機來講並不那麼重要,但有了高級語言及函數,堆棧成爲了計算機的基礎功能;markdown

    • enter 框架

      • pushl %ebp函數

      •  movl %esp,%ebpui

    • leave this

      • movl %ebp,%espspa

      • popl %ebp操作系統

    • 函數參數傳遞機制和局部變量存儲線程

  • 中斷,多道程序操做系統的基點,沒有中斷機制程序只能從頭一直運行結束纔有可能開始運行其餘程序。

2. 堆棧

  • 堆棧是C語言程序運行時必須的一個記錄調用路徑和參數的空間。
    • 函數條用框架
    • 傳遞參數
    • 保存返回地址
    • 提供局部變量空間...
  • C語言編譯器對堆棧的使用有一套的規則
  • 瞭解對站存在的目的和編譯器對堆棧使用的規則是理解操做系統一些關鍵性代碼的基礎。

  • 堆棧相關寄存器:

    esp:堆棧指針(stack pointer),指向系統棧最上面一個棧幀的棧頂
  • ebp: 基址指針(base pointer),指向系統棧最上面一個棧幀的底部
  • cs:eip:指令寄存器(extended instruction pointer),指向下一條等待執行的指令地址

3. 堆棧操做

  • push:以字節爲單位將數據(對於32位系統能夠是4個字節)壓入棧,從高到低按字節依次將數據存入ESP-一、ESP-二、ESP-三、ESP-4的地址單元。
  • pop: 過程與PUSH相反。
  • call: 用來調用一個函數或過程,此時,下一條指令地址會被壓入堆棧,以備返回時能恢復執行下條指令。
  • leave:當調用函數調用時,通常都有這兩條指令pushl %ebpmovl %esp,%ebp,leave是這兩條指令的反操做。
  • ret: 從一個函數或過程返回,以前call保存的下條指令地址會從棧內彈出到EIP寄存器中,程序轉到CALL以前下條指令處執行。
call指令的兩個做用:
- 將下一條指令的地址A保存在棧頂 - 設置eip指向被調用程序代碼開始處


4. 函數堆棧框架

  • 執行call function

    cs:eip原來的值指向call下一條指令,該值被保存到棧頂 cs:eip的值指向function的入口地址
  • 進入function

    pushl %ebp  //意爲保存調用者的棧幀地址
    movl %esp, %ebp //初始化function的棧幀地址
    而後函數體中的常規操做
  • 退出function

    movl %ebp,%esp
    popl %ebp
    ret

2、實驗

(一)mykernel實驗

實驗步驟

(1)進入實驗樓,打開shell以後按照說明輸入

(2)查看源代碼

a.查看mymain.c

以前的#include...都是硬件初始化用到的語句,而截圖部分是是「操做系統」開始執行的入口

從代碼可見,每循環10 000次,打印一句話。

b.查看myinterrupt.c

每執行一次,都會執行一次時鐘中斷

(二)在mykernel基礎上構造一個簡單地操做系統內核

(1)mypcd.h源代碼

/* * linux/mykernel/mypcb.h * Kernel internal PCB types * Copyright (C) 2013 Mengning */ #define MAX_TASK_NUM 4 #define KERNEL_STACK_SIZE 1024*8 /* CPU-specific state of this task */ struct Thread { unsigned long ip;//保存eip unsigned long sp;//保存esp }; typedef struct PCB{ int pid; volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ char 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結構體,用於存儲當前進程中正在執行的線程的eip和esp。
  • PCB結構體中:
    • pid:進程號
    • state:進程狀態,在模擬系統中,全部進程控制塊信息都會被建立出來,其初始化值就是-1,若是被調度運行起來,其值就會變成0
    • stack:進程使用的堆棧
    • thread:當前正在執行的線程信息
    • task_entry:進程入口函數(就像通常咱們用的進程定義的是main)
    • next:指向下一個PCB,模擬系統中全部的PCB是以鏈表的形式組織起來的。
  • 函數的聲明my_schedule:調度器。它在my_interrupt.c中實現,在mymain.c中的各個進程函數會根據一個全局變量的狀態來決定是否調用它,從而實現主動調度。

(2)mymain.c:內核初始化和0號進程啓動

/* * 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 (初始化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;//定義0號進程的入口:myprocess task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; task[pid].next = &task[pid];//因爲0號進程初始化時只有這一個進程,因此next指向本身 /*fork more process (建立更多其餘的進程)*/ for(i=1;i<MAX_TASK_NUM;i++) { memcpy(&task[i],&task[0],sizeof(tPCB)); task[i].pid = i; task[i].state = -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( //%0表示參數thread.ip,%1表示參數thread.sp。 "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp 把參數thread.sp放到esp中*/ "pushl %1\n\t" /* push ebp 因爲當前棧是空的,esp與ebp指向相同,因此等價於push ebp*/ "pushl %0\n\t" /* push task[pid].thread.ip */ "ret\n\t" /* pop task[pid].thread.ip to eip */ "popl %%ebp\n\t" : : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/ ); } void my_process(void) { int i = 0; 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號進程的初始化和啓動(狀態是正在運行、入口是myprocess,進程剛啓動時next指向本身)。
  • 建立了其它的多個進程,在初始化這些進程的時候能夠直接利用0號進程的代碼。
  • my_process函數:在模擬系統裏,每一個進程的函數代碼都是同樣的。my_process 在執行時,打印出當前進程的id,可以看到當前哪一個進程正在執行。每循環10000000次檢查全局標誌變量my_need_sched判斷是否須要調度,一旦發現其值爲1,就調用my_schedule完成進程的調度。

(3)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 //task爲空,即發生錯誤時返回 || my_current_task->next == NULL) { return; } printk(KERN_NOTICE ">>>my_schedule<<<\n"); /* schedule */ next = my_current_task->next;//把當前進程的下一個進程賦給next prev = my_current_task;//當前進程爲prev if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */ { /* switch to next process */ /*若是下一個進程的狀態是正在執行的話,就運用if語句中的代碼表示的方法來切換進程*/ asm volatile( "pushl %%ebp\n\t" /* save ebp 保存當前進程的ebp*/ "movl %%esp,%0\n\t" /* save esp 把當前進程的esp賦給%0(指的是thread.sp),即保存當前進程的esp*/ "movl %2,%%esp\n\t" /* restore esp 把%2(指下一個進程的sp)放入esp中*/ "movl $1f,%1\n\t" /* save eip $1f是接下來的標號「1:」的位置,把eip保存下來*/ "pushl %3\n\t" /*把下一個進程eip壓棧*/ "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) ); 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); /* switch to new process */ asm volatile( "pushl %%ebp\n\t" /* save ebp */ "movl %%esp,%0\n\t" /* save esp */ "movl %2,%%esp\n\t" /* restore esp */ "movl %2,%%ebp\n\t" /* restore ebp */ "movl $1f,%1\n\t" /* save eip */ "pushl %3\n\t" /*把當前進程的入口保存起來*/ "ret\n\t" /* restore eip */ : "=m" (prev->thread.sp),"=m" (prev->thread.ip) : "m" (next->thread.sp),"m" (next->thread.ip) ); } return; }

3、總結:

1.操做系統「兩劍」:中斷上下文、進程上下文的切換.

2.操做系統的核心功能就是:進程調度和中斷機制,經過與硬件的配合實現多任務處理,再加上上層應用軟件的支持,最終變成能夠使用戶能夠很容易操做的計算機系統。

3.進程切換機制中包含esp的切換、堆棧的切換。從esp能夠找到進程的描述符;堆棧中ebp的切換,肯定了當前變量空間屬於哪一個進程。

相關文章
相關標籤/搜索