網易雲課堂linux內核分析(二)

前言

這是第二週的報告。本週的實驗是:完成一個簡單的時間片輪轉多道程序內核代碼,代碼見視頻中或從mykernel找。linux

老師已經爲咱們搭好了實驗的環境——linux3.9.4下一個極其迷你的系統。咱們不用去關心別的東西,只須要知道這個迷你係統從my_start_kernel函數開始,系統時鐘中斷會執行my_timer_handler函數。剩下的留給咱們本身發揮。同時,實驗要寫的代碼已經給出,因此完成這個實驗的難度不大。實驗的關鍵是理解所給的代碼爲何要這麼寫,也就是理解程序如何切換。git

代碼與分析

mypcb.h定義進程的屬性和信息。github

#define MAX_TASK_NUM 10                //這個系統最多十個進程
#define KERNEL_STACK_SIZE 1024*8    //每一個進程的棧的大小

//進程的各類狀態
#define MY_RUNNING 1     
#define MY_SLEEP 2
#define MY_DEAD 3

//用於進程調度時,保存它棧地址和代碼地址
struct Thread
{
    unsigned long ip;    
    unsigned long sp;
};

typedef struct PCB
{
    int pid;            //進程總有個編號吧?
    volatile long state;
    char stack[KERNEL_STACK_SIZE];
    struct Thread thread;
    unsigned long entry;    //進程第一次執行開始的地方
    struct PCB *next;        //用於構造進程鏈表
    unsigned long priority;    //暫時沒用到
    
}tPCB;


void my_schedule(void);

總的來講my_start_kernel就是建立各個進程,而且進入0號進程。函數

#include "mypcb.h"    //PCB結構信息

tPCB my_task[MAX_TASK_NUM];    //建立若干個PCB。也就是建立若干個任務
tPCB *my_current = NULL;        //用來表示當前任務的指針。

//是否要進行程序切換的標記。1爲須要,0相反。
//這個變量由my_timer_handler和my_process修改
extern volatile int my_need_sched;   

//建立的進程都執行這個函數
void my_process(void)
{
    unsigned count = 0;
    unsigned slice = sizeof(count);
    while (1)
    {
        count += 1;
        
        //程序執行必定時間後檢查是否要進行程序切換,不須要則輸出信息
        if (!(count<<slice))
        {
            if (my_need_sched == 1)
            {
                my_need_sched = 0;
                my_schedule();
            }
            else
            {
                printk(KERN_NOTICE "process %d is running\n", my_current->pid);
            }
        } 
    }
}

//迷你係統從這個函數開始執行。
void __init my_start_kernel(void)
{
    int i;

    /* init task 0 */
    //初始化第一個進程,0號
    my_task[0].pid = 0;
    my_task[0].state = MY_RUNNING;
    my_task[0].thread.sp = (unsigned long)&my_task[0].stac[KERNEL_STACK_SIZE-1];
    //設置程序的入口,也就是my_process的地址
    my_task[0].entry = my_task[0].thread.ip = (unsigned long)my_process; 
    //環形鏈表
    my_task[0].next = &my_task[0];

    /* then init other "processes" */
    //初始化其餘進程。
    for (i = 1; i < MAX_TASK_NUM; i++)
    {
        memcpy(&my_task[i], &my_task[0], sizeof(tPCB));
        my_task[i].pid = i;
        my_task[i].state = MY_SLEEP;    //只有0號醒着,其它都睡着了
        my_task[i].thread.sp += (unsigned long)sizeof(tPCB);
        /* to make the list a big loop */
        //環形,鏈表最後一個元素指向第一個元素
        my_task[i].next = my_task[i-1].next;
        my_task[i-1].next = &my_task[i];

    }
    
    /* going to switch to task 0! */
    printk(KERN_NOTICE "main going to switch to task 0\n");
    //好緊張,要開始切換了。
    my_current = &my_task[0];
    asm volatile(
        "movl %1, %%esp\n\t"    //將esp和ebp設置爲任務0的棧
        "movl %1, %%ebp\n\t"
        "pushl %0\n\t"        //不能直接修改eip,因此先將任務0的地址入棧
        "ret\n\t"                //再經過ret賦值給eip
        :
        :"c"(my_task[0].thread.ip), "d"(my_task[0].thread.sp)
    );
}

myinterupt.c實現了時鐘中斷處理和程序調度。oop

//記錄時鐘中斷了多少次
volatile unsigned long time_count = 0;
volatile int my_need_sched = 0;
//當前進程的PCB
extern tPCB *my_current;


/*
 * Called by timer interrupt.
 */
//每次時鐘中斷都會執行它
void my_timer_handler(void)
{
    time_count += 1;
    //若是若干次中斷後須要程序調度,修改my_need_sched。
    if ((!(time_count<<COUNT_SLICE)) && my_need_sched != 1)
    {
        my_need_sched = 1;    //my_process會檢查它是否爲1,如果,就執行下面的調度程序(程序主動調度)
    }
}

//
void my_schedule(void)
{
    tPCB *next = my_current->next;    //將要執行的進程
    tPCB *pre = my_current;

    //錯誤檢查
    if (!next)
    {
        printk(KERN_NOTICE "switch to NULL\n");
        while (1);
    }

    printk(KERN_NOTICE "task %d is going to task %d\n", my_current->pid, next->pid);
    my_current = next;

    if (next->state == MY_RUNNING)
    {
        //若是要切換執行的程序已經醒了
       asm volatile (
           "pushl %%ebp\n\t"        //保存當前ebp,esp,eip
           "movl %%esp, %0\n\t"
           "movl $1f, %1\n\t"
           "movl %2, %%esp\n\t"    //切換到將要運行的棧
           "pushl %3\n\t"          //不能直接修改eip,因此經過給壓棧再ret方式賦值
           "ret\n\t"                //切換eip。如今已是另一個進程了
           "1:\n\t"                 //切換後從這裏開始
           "popl %%ebp\n\t"        //恢復這個進程被切換以前的ebp
           :"=m"(pre->thread.sp), "=m"(pre->thread.ip)
           :"m"(next->thread.sp), "m"(next->thread.ip)
       ); 
        
        
    }
    else if (next->state == MY_SLEEP)
    {
        //若是要切換執行的程序尚未運行過。過程和上面的切換差很少
        next->state = MY_RUNNING;    //先叫醒它
        asm volatile (
            "pushl %%ebp\n\t"
            "movl %%esp, %0\n\t"
            "movl $1f, %1\n\t"
            "movl %2, %%esp\n\t"
            "movl %2, %%ebp\n\t       //新進程的棧是空的,因此要設置ebp
            "pushl %3\n\t"
            "ret\n\t"
            "1:\n\t"            //被切換的進程下次執行從這裏開始,而剛被叫醒的進程從my_process開始。
            "popl %%ebp\n\t"     //這個被切換的進程須要恢復ebp
            :"=m"(pre->thread.sp), "=m"(pre->thread.ip)
            : "m"(next->thread.sp), "m"(next->thread.ip) 
        );
    }
}

結果

進程從0到9,又從9到0循環執行。spa

圖片描述


總結

瞭解了進程如何進行初始化和切換:實質就是寄存器和棧的切換。
問題:切換不須要保存當前的eax等通用寄存器嗎?指針

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

相關文章
相關標籤/搜索