首先回顧了下堆棧相關的知識,堆棧機制是高級語言能夠運行的一個基礎,這一塊須要重點掌握。函數發生調用時,如圖
html
call指令:將eip的按順序執行的下一條指令(由於在執行call的時候,eip保存的是call語句下一條指令的地址)的地址保存在當前棧頂,而後設置eip的值爲要跳轉到的函數的開始的地址
ret指令:將以前使用call指令的保存在棧裏面的地址恢復到eip中去。linux
用本身的Ubuntu來搭建實驗所須要的環境。可是在使用用apt-get命令安裝軟件包時,總報錯:E:could not get lock /var/lib/dpkg/lock -open等,因而上網搜尋了一下緣由,多是有另一個程序正在運行,致使資源被鎖不可用。而致使資源被鎖的緣由,多是上次安裝時沒正常完成,而致使出現此情況。解決辦法就是輸入命令git
sudo rm /var/cache/apt/archives/lock sudo rm /var/lib/dpkg/lock
而後就能夠開始安裝編譯qemu了(linux下一個相似於虛擬機的軟件),步驟的命令以下:(由於要輸入的命令挺多的,因此先安裝了vmtools,這一步驟參考:Ubuntu16安裝VM toolsgithub
sudo apt-get install qemu # install QEMU sudo ln -s /usr/bin/qemu-system-i386 /usr/bin/qemu wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.9.4.tar.xz # download Linux Kernel 3.9.4 source code wget https://raw.github.com/mengning/mykernel/master/mykernel_for_linux3.9.4sc.patch # download mykernel_for_linux3.9.4sc.patch xz -d linux-3.9.4.tar.xz tar -xvf linux-3.9.4.tar cd linux-3.9.4 patch -p1 < ../mykernel_for_linux3.9.4sc.patch make allnoconfig make
在最後的編譯環節出錯了,沒有找到 compiler-gcc5.h 這樣一個文件,上網查了查,是個人Ubuntu系統太新的緣由。須要咱們本身手動去網上下一個compiler-gcc5.h放入要編譯內核模塊的內核代碼的include/linux下,找了好久沒有找到compiler-gcc5.h這個文件,從新下載gcc5.1.0這個版本又下載不下來,因此就直接用了另一種辦法—— **將咱們下載的/linux-3.9.4/include/linux/中的compiler-gcc4.h複製了一份而後更名爲compiler-gcc5.h,最後編譯成功。
當咱們執行命令編程
qemu -kernel arch/x86/boot/bzImage
以後,就彈出一個新的窗口,不斷的輸出信息,表示啓動內核了,如圖
數組
到/mykernel目錄下查看mymain.c文件,如圖
函數
void __init my_start_kernel(void) { int i = 0; while(1) { i++; if(i%100000 == 0) printk(KERN_NOTICE "my_start_kernel here %d \n",i); } }
有一個my_start_kernel()函數,故名思義,應該就是內核開始的函數。裏面寫了一個死循環,變量i不停的自加1,每當加到1000000的整數倍的時候就打印出當前的i的值。而後查看另外一個文件myinterrupt.c,如圖
學習
定義了一個my_timer_handler()的函數this
/* * Called by timer interrupt. */ void my_timer_handler(void) { printk(KERN_NOTICE "\n>>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<\n\n"); }
只有一條打印語句, 這裏有一點要注意,內核編程的打印語句是printk而不是printf。
因爲CPU處理速度很快,因此屏幕不停的交替打印出輸出信息,而且一閃而過。
上面這是一個極其簡單的時間片,下面分析一個稍微複雜的進程切換的程序代碼。一共三個文件,分別是mypcb.h,mymain.c,myinterrupt.c。在學習分析代碼的時候都用本身學習到的和本身的理解對代碼進行了註釋,下面給出相關代碼和註釋。
先是mypcb.h中的代碼,這個頭文件的目的是定義一下進程控制塊指針
#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; //進程的id 進程的狀態 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);
文件的最後聲明瞭一個schedule函數,即調度器。
第二個main.c中的代碼以下
#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]; //PCB的數組task tPCB * my_current_task = NULL; //當前task的指針 volatile int my_need_sched = 0; //是否須要調度 void my_process(void); //聲明瞭一個my_process函數 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;//初始化爲my_process,實際上爲mystartkernel task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; task[pid].next = &task[pid];//剛一啓動只有0號進程 /*fork more process */ for(i=1;i<MAX_TASK_NUM;i++) { memcpy(&task[i],&task[0],sizeof(tPCB)); //將0號進程狀態copy過來 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]; //新fork的進程加到進程列表的尾部 } //建立了MAX_TASK_NUM個進程 /* start process 0 by task[0] */ pid = 0; my_current_task = &task[pid]; //當前的進程是0號進程 asm volatile( "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */ //第一號(task[pid].thread.sp)參數放到esp "pushl %1\n\t" /* push ebp */ //當前堆棧空,esp==ebp "pushl %0\n\t" /* push task[pid].thread.ip */ //當前ip壓棧 "ret\n\t" /* pop task[pid].thread.ip to eip */ //ret後0號進程正式啓動 "popl %%ebp\n\t" : : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/ ); //構建起來了cpu的運行環境(0號進程設定的堆棧和0號進程的入口) };內核初始化完成,把0號進程啓動起來了 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裏面的代碼
#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; //把全局的東西extern來 volatile int time_count = 0; //時間計數 void my_timer_handler(void) { #if 1 if(time_count%1000 == 0 && my_need_sched != 1) //時鐘中斷髮生1000次而且my_need_sched不爲1(設置時間片的大小,時間片用完時設置一下調度標誌,時間片變小調度更頻繁) { printk(KERN_NOTICE ">>>my_timer_handler here<<<\n"); my_need_sched = 1; //當進程執行到的時候,發現爲1,調度一次,執行一下my_schedule } time_count ++ ; #endif return; } 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; //當前進程的下一個進程賦給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; }
最後將這些代碼複製到對應的文件裏面去,保存(本來沒有mypcb.h文件,因此要另外建立一個mypcb.h),再回到~/linux-3.9.4目錄下,輸入命令 make
編譯一遍,再執行命令
qemu -kernel arch/x86/boot/bzImage
就可運行剛剛的代碼了,結果如圖所示
1.__init my_start_kernel(void)這個函數是幹什麼的,有什麼用?第一個程序,代碼最精簡的那個,我看mymain.c和myinterrupt.c裏面也沒寫什麼特別的代碼,可是編譯運行以後就能實現相互交替的打印出來。因此我在想是__init my_start_kernel(void)這個函數有什麼特別的嗎?上網查詢資料也沒有查到想要的結果,加上本身對linux內核這一塊的確是很陌生。因此這個問題未解決。