linux操作系统分析实验—基于mykernel的时间片轮转多道程序实现与分析
linux操作系統(tǒng)分析實(shí)驗(yàn)—基于mykernel的時(shí)間片輪轉(zhuǎn)多道程序?qū)崿F(xiàn)與分析
學(xué)號(hào)384 原創(chuàng)作業(yè)轉(zhuǎn)載請(qǐng)注明出處+中國(guó)科學(xué)技術(shù)大學(xué)孟寧老師的Linux操作系統(tǒng)分析 https://github.com/mengning/linuxkernel/
實(shí)驗(yàn)環(huán)境介紹與搭建
1.mykernel簡(jiǎn)介
這個(gè)是由孟寧老師建立的一個(gè)用于開(kāi)發(fā)自己的操作系統(tǒng)內(nèi)核的平臺(tái),它基于Linux Kernel 3.9.4 source code。您可以在這里找到mykernel的源代碼https://github.com/mengning/mykernel并按照上面的指南部署到您的系統(tǒng)上。也可以使用實(shí)驗(yàn)樓http://www.shiyanlou.com/courses/195提供的虛擬機(jī),上面已經(jīng)部署好了這個(gè)平臺(tái)。本文是使用自己的虛擬機(jī)中ubuntu操作系統(tǒng),部署環(huán)境完成,具體實(shí)現(xiàn)見(jiàn)下。
2.環(huán)境搭建
為了便于管理,首先新建MyKernel文件夾,然后下載好kernel內(nèi)核linux-3.9.4以及kernel補(bǔ)丁包mykernel_for_linux3.9.4sc.patch。
wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.9.4.tar.xz wget https://raw.github.com/mengning/mykernel/master/mykernel_for_linux3.9.4sc.patch并使用tar xvf linux-3.9.4.jar將壓縮包解壓。
進(jìn)入linux-3.9.4文件中,使用下載好的補(bǔ)丁包開(kāi)始打補(bǔ)丁。
patch -p1 < ../mykernel_for_linux3.9.4sc.patch
然后進(jìn)行編譯步驟。首先使用make allnoconfig進(jìn)行復(fù)位,然后make進(jìn)行編譯。
這里make過(guò)程中可能遇到的error,是報(bào)錯(cuò)沒(méi)有compiler-gcc5.h這個(gè)文件,原因是linux-3.9.4內(nèi)核版本老,沒(méi)有此文件。解決方式是:進(jìn)入linux-3.9.4文件中include/linux文件夾中,使用cp compiler-gcc4.h compiler-gcc5.h將compiler-gcc4.h拷貝給compiler-gcc5.h。
然后執(zhí)行編程make并等待完成。
接下來(lái)安裝qemu,并使用qemu測(cè)試內(nèi)核是否正常運(yùn)行。若顯示下面的彈窗內(nèi)容,表明linux-3.9.4布置完成。
sudo apt-get install qemu sudo ln –s /usr/bin/qemu-system-i386 /usr/bin/qemu qemu -kernel arch/x86/boot/bzImage實(shí)驗(yàn)操作
1.實(shí)驗(yàn)操作主要是實(shí)現(xiàn)一個(gè)時(shí)間片輪轉(zhuǎn)多道程序。
實(shí)驗(yàn)步驟:從https://github.com/mengning/mykernel這里獲取實(shí)驗(yàn)用的源代碼,主要就這三個(gè)文件:mypcb.h,myinterrupt.c和mymain.c。將這三個(gè)文件拷貝到mykernel平臺(tái)中,即要覆蓋前文所述的mykernel文件夾下mymain.c和myinterrupt.c,并新增mypcb.h。
然后進(jìn)行編譯運(yùn)行。
make allnoconfig make qemu -kernel arch/x86/boot/bzImage2.代碼分析
主要分析實(shí)驗(yàn)中改寫(xiě)的三個(gè)文件,其作用如下:
mypcb.h : 此文件主要定義了一個(gè)PCB結(jié)構(gòu)體,即所謂的進(jìn)程管理塊,用來(lái)記錄進(jìn)程的有關(guān)信息。
mymain.c: 該文件就是完成了內(nèi)核的初始化工作,并且創(chuàng)建了4個(gè)進(jìn)程,進(jìn)程從0號(hào)開(kāi)始執(zhí)行,并且根據(jù)標(biāo)志位判斷進(jìn)程是否需要調(diào)度,當(dāng)標(biāo)志位為1時(shí)表示進(jìn)程需要調(diào)度,此時(shí)會(huì)執(zhí)行my_schedule()方法來(lái)完成相應(yīng)的調(diào)度。
myinterrupt.c:此文件主要是產(chǎn)生時(shí)鐘中斷,用一個(gè)時(shí)間計(jì)數(shù)器周期性的檢查循環(huán)條件,當(dāng)條件滿足時(shí)便會(huì)產(chǎn)生中斷,并將進(jìn)程調(diào)度標(biāo)志位置1,當(dāng)標(biāo)志位為1時(shí),進(jìn)程便會(huì)執(zhí)行my_schedule()方法,首先將當(dāng)前進(jìn)程的信息通過(guò)嵌入式匯編語(yǔ)句保存到堆棧當(dāng)中,然后將下一個(gè)進(jìn)程的地址賦給當(dāng)前運(yùn)行程序的指針,完成調(diào)度。
具體代碼如下:
mypcb.h:
/** linux/mykernel/mypcb.h** Kernel internal PCB types** Copyright (C) 2013 Mengning**/#define MAX_TASK_NUM 4 #define KERNEL_STACK_SIZE (unsigned long)1024*2 /* CPU-specific state of this task */ struct Thread {unsigned long ip;unsigned long sp; };typedef struct PCB{int 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;unsigned long task_entry;struct PCB *next; }tPCB;void my_schedule(void);該頭文件中定義了一個(gè)兩個(gè)結(jié)構(gòu)體,第一個(gè)是struct Thread,它用來(lái)存儲(chǔ)線程的ip和sp。另外一個(gè)是PCB(進(jìn)程控制塊),這是操作系統(tǒng)中一個(gè)很重要的數(shù)據(jù)結(jié)構(gòu),所有關(guān)于進(jìn)程的信息都存儲(chǔ)在PCB中,這里代碼中簡(jiǎn)化了PCB的結(jié)構(gòu),只給出了一些最基本的信息:
pid:進(jìn)程號(hào)
state:進(jìn)程狀態(tài),在模擬系統(tǒng)中,所有進(jìn)程控制塊信息都會(huì)被創(chuàng)建出來(lái),其初始化值就是-1,如果被調(diào)度運(yùn)行起來(lái),其值就會(huì)變成0
stack:進(jìn)程使用的堆棧
thread:當(dāng)前正在執(zhí)行的線程信息
task_entry:進(jìn)程入口函數(shù)
next:指向下一個(gè)PCB,模擬系統(tǒng)中所有的PCB是以鏈表的形式組織起來(lái)的。
最后還有一個(gè)函數(shù)的聲明 my_schedule,它的實(shí)現(xiàn)在my_interrupt.c中,在mymain.c中的各個(gè)進(jìn)程函數(shù)會(huì)根據(jù)一個(gè)全局變量的狀態(tài)來(lái)決定是否調(diào)用它,從而實(shí)現(xiàn)主動(dòng)調(diào)度。
mymain.c:
/** 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*/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];/*fork more process */for(i=1;i<MAX_TASK_NUM;i++){memcpy(&task[i],&task[0],sizeof(tPCB));task[i].pid = i;//*(&task[i].stack[KERNEL_STACK_SIZE-1] - 1) = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-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("movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */"pushl %1\n\t" /* push ebp */"pushl %0\n\t" /* push task[pid].thread.ip */"ret\n\t" /* pop task[pid].thread.ip to eip */: : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/); } int i = 0;void my_process(void) { 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);} } }代碼中看出,這里講PCB實(shí)現(xiàn)成了一個(gè)環(huán)形隊(duì)列,每次創(chuàng)建新的進(jìn)程時(shí),都將該進(jìn)程插入到隊(duì)列的最后一個(gè)位置,然后將該進(jìn)程指向第一個(gè)進(jìn)程。創(chuàng)建好進(jìn)程之后,該函數(shù)中最重要的一段代碼出現(xiàn)了,該部分利用嵌入式匯編來(lái)啟動(dòng)0號(hào)進(jìn)程。
asm volatile("movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */"pushl %1\n\t" /* push ebp */"pushl %0\n\t" /* push task[pid].thread.ip */"ret\n\t" /* pop task[pid].thread.ip to eip */: : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/首先將PCB中的sp信息放入esp寄存器,然后將ebp壓棧(因?yàn)榇藭r(shí)進(jìn)程堆棧為空棧,所以壓入esp相當(dāng)于ebp),第三第四句我們將eip壓棧然后ret(即pop eip),下一條指令就會(huì)跳轉(zhuǎn)到0號(hào)進(jìn)程的第一條指令開(kāi)始執(zhí)行,在這里就是my_process()。my_process干了一件很簡(jiǎn)單的事情,就是循環(huán)等待并print進(jìn)程id,然后觀察變量my_need_sched是否為1,如果是則主動(dòng)進(jìn)入調(diào)度函數(shù)my_schedule開(kāi)始執(zhí)行。
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 1if(time_count%1000 == 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 */{/* switch to next process */asm volatile( "pushl %%ebp\n\t" /* save ebp */"movl %%esp,%0\n\t" /* save esp */"movl %2,%%esp\n\t" /* restore 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)); 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; }mykernel提供了時(shí)鐘中斷機(jī)制,周期性執(zhí)行my_time_handler中斷處理程序,該函數(shù)定時(shí)觀察my_need_sched是否不等于1,如果是則將其置為1,使myprocess執(zhí)行my_schedule(),這里 my_timer_handler 函數(shù)會(huì)被內(nèi)核周期性的調(diào)用,每調(diào)用1000次,就去將全局變量my_need_sched的值修改為1,通知正在執(zhí)行的進(jìn)程執(zhí)行調(diào)度程序my_schedule。在my_schedule函數(shù)中,完成進(jìn)程的切換。進(jìn)程的切換分兩種情況,一種情況是下一個(gè)進(jìn)程沒(méi)有被調(diào)度過(guò),另外一種情況是下一個(gè)進(jìn)程被調(diào)度過(guò),可以通過(guò)下一個(gè)進(jìn)程的state知道其狀態(tài)。進(jìn)程切換依然是通過(guò)內(nèi)聯(lián)匯編代碼實(shí)現(xiàn),無(wú)非是保存舊進(jìn)程的eip和堆棧,將新進(jìn)程的eip和堆棧的值存入對(duì)應(yīng)的寄存器中。
總結(jié)
參考了一些同學(xué)的博客,收獲很大。本次實(shí)驗(yàn)的重點(diǎn)就是理解時(shí)間轉(zhuǎn)輪片中斷和my_schedule()函數(shù)中進(jìn)程的調(diào)度機(jī)制,即如何利用時(shí)間轉(zhuǎn)輪片的中斷機(jī)制以及寄存器和堆棧的使用進(jìn)行保存現(xiàn)場(chǎng)和恢復(fù),最終實(shí)現(xiàn)操作系統(tǒng)進(jìn)程的切換。通過(guò)這次實(shí)驗(yàn),對(duì)于操作系統(tǒng)的核心功能了解更加深入一點(diǎn):進(jìn)程調(diào)度和中斷機(jī)制,通過(guò)與硬件的配合實(shí)現(xiàn)多任務(wù)處理,再加上上層應(yīng)用軟件的支持,最終變成可以使用戶可以很容易操作的計(jì)算機(jī)系統(tǒng)。
總結(jié)
以上是生活随笔為你收集整理的linux操作系统分析实验—基于mykernel的时间片轮转多道程序实现与分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: LeetCode刷题之旅
- 下一篇: 跟踪分析Linux内核5.0系统调用处理