操作系統是如何工作的
內核實現
本周學習內容是實現一個簡單的時間片輪轉多道程序內核。
首先需要在Linux虛擬機中再構建一個虛擬的x86CPU硬件平臺。這個平臺的構建我們利用了部分Linux 3.9.4版本源代碼以及網上的內核源代碼。
內核搭建好后其啟動效果如下:
而后,我們需要在搭建好的內核的基礎上,修改內核的源程序,構建我們所需的程序內核。
首先我們增加一個mypcb.h頭文件,用于定義進程控制塊(PCB)的數據結構。我們知道,在操作系統中,每一個進程都依賴于一個PCB,以用于進程調度,來實現進程的并發執行。因此,我們的PCB結構定義如下:
struct Thread {unsigned long ip; //進程eipunsigned long sp; //進程esp
};typedef struct PCB{int pid; //進程idvolatile long state; //進程狀態char stack[KERNEL_STACK_SIZE]; //進程堆棧struct Thread thread; //unsigned long task_entry; //進程的起始入口地址struct PCB *next; //指向下一個進程
}tPCB;void my_schedule(void); //進行進程調度
然后,我們修改一下mymain.c文件,此文件為內核的入口,用于內核各組件的初始化。我們通過這段程序來啟動內核的0號進程,其代碼如下:
#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 = 0void my_process(void);void __init my_start_kernel(void)
{int pid = 0int i;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];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];}pid = 0;my_current_task = &task[pid];asm volatile("movl %1,%%esp\n\t" "pushl %1\n\t" "pushl %0\n\t" "ret\n\t" "popl %%ebp\n\t": : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) );
}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);} }
}
另外還要修改myinterrupt.c文件,增加進程調度函數my_schedule(void),以進行進程切換。其代碼如下:
#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;void my_timer_handler(void)
{
#if 1if(time_count%10000 == 0 && my_need_sched != 1){printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");my_need_sched = 1;} time_count ++ ;
#endifreturn;
}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 */{asm volatile( "pushl %%ebp\n\t" "movl %%esp,%0\n\t" "movl %2,%%esp\n\t" "movl $1f,%1\n\t" "pushl %3\n\t" "ret\n\t" "1:\t" "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" "movl %%esp,%0\n\t" "movl %2,%%esp\n\t" "movl %2,%%ebp\n\t" "movl $1f,%1\n\t" "pushl %3\n\t" "ret\n\t" : "=m" (prev->thread.sp),"=m" (prev->thread.ip): "m" (next->thread.sp),"m" (next->thread.ip)); } return;
}
可以看出,整個內核的運行過程是:
首先啟動內核,啟動0號進程進行初始化,其中0號進程的啟動部分使用了內嵌匯編代碼編寫:
asm volatile( "movl %1,%%esp\n\t" /*將進程原堆棧棧頂的地址(這里是初始化的值)存入ESP寄存器 */ "pushl %1\n\t" /* 將當前EBP寄存器值入棧 */ "pushl %0\n\t" /* 將當前進程的EIP(這里是初始化的值)入棧*/ "ret\n\t" /* ret命令正好可以讓入棧的進程EIP保存到EIP寄存器中*/ "popl %%ebp\n\t" /*這里永遠不會被執行,知識與前面push指令結對出現,是一種編碼習慣*/: : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) );
當進程0的時間片用完(my_process中的i累加到100000000),將利用進程調度函數,運行進程1,其調度過程執行如下:
if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */{asm volatile( "pushl %%ebp\n\t" "movl %%esp,%0\n\t" "movl %2,%%esp\n\t" "movl $1f,%1\n\t" "pushl %3\n\t" "ret\n\t" "1:\t" "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" "movl %%esp,%0\n\t" "movl %2,%%esp\n\t" "movl %2,%%ebp\n\t" "movl $1f,%1\n\t" "pushl %3\n\t" "ret\n\t" : "=m" (prev->thread.sp),"=m" (prev->thread.ip): "m" (next->thread.sp),"m" (next->thread.ip));}
由于進行1并未被執行過,其調度過程中執行else部分的進程調度代碼。
同理,當進程1的時間片用完,將再次利用調度函數將運行進程0,此時進程0是從新被執行,將使用if部分的進程調度代碼。
由此,每過一個時間片(通過my_process中i的不斷模100000000累加),內核將不斷在進程0與進程1之間切換。
問題與解決
本周在搭建內核環境時,在make的過程中,gcc報錯。
原因是因為找不到compiler-gcc7.h頭文件,進入到文件目錄下,發現確實沒有該文件,只有compiler-gcc.h、compiler-gcc3.h、compiler-gcc4.h三個文件,猜測可能是因為下載的Linux源文件版本不同的原因。在網上下載compiler-gcc7.h文件后重新編譯,內核可正常運行。