這是第二週的報告。本週的實驗是:完成一個簡單的時間片輪轉多道程序內核代碼,代碼見視頻中或從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