操作系統工作流程
計算機有三個關鍵性機制:存儲程序計算機,堆棧機制和中斷機制
第一章中已經重點學習了存儲程序的計算機,接下來我們重點學習堆棧機制和中斷機制。
堆棧機制
堆棧機制是高級語言可以實現的基礎機制,是C語言程序運行時必須使用的記錄函數調用路徑和參數存儲的空間,他的具體作用有:記錄函數調用框架,傳遞函數參數,保存返回值的地址,提供函數內部局部變量的存儲空間等
堆棧相關的寄存器有:
1.ESP(堆棧指針寄存器)以及EBP(記錄當前函數調用基址的基址指針寄存器)
2.CS:EIP:總是指向下一條的指令地址。順序執行時,總是指向地址連續的下一條指令;跳轉/分支執行時,CS:EIP的值會根據程序需要被修改。
3.EAX:保存返回值。如果有多個返回值,則返回一個內存地址。
堆棧相關操作:
push:棧頂地址減少4個字節,並將操作數放入棧頂存儲單元
pop:棧頂地址增加四個字節,並將棧頂存儲單元的內容放入操作數
call:將當前CS:EIP的值壓入棧頂,CS;EIP指向被調用函數的入口地址
ret:從棧頂彈出原來保存在這裏的CS:EIP的值,放入CS:EIP的值
enter和leave:一步對函數調用堆棧框架的建立和拆除進行封裝
參數傳遞;從左到右依次壓棧
中斷機制實驗
虛擬X86的cpu硬件平臺搭建
本次實驗在實驗樓環境中進行,實驗代碼如下
cd LinuxKernel/linux-3.9.4
rm -rf mykernel
patch -p1 < ../mykernel_for_linux3.9.4sc.patch
make allnoconfig
make #編譯內核
qemu -kernel arch/x86/boot/bzImage
搭建起來後內核的啟動效果如下
在mykernel基礎上完成一個簡單的時間片輪轉多道程序
在前面試驗的基礎上cd mykernel ,增加一個mypcb.h的頭文件
mypcb.h
#define MAX_TASK_NUM 4 #define KERNEL_STACK_SIZE 1024*8 /* CPU-specific state of this task */ struct Thread { unsigned long ip; unsigned long sp; }; typedef struct PCB{ int pid; volatile long state; char stack[KERNEL_STACK_SIZE]; /* CPU-specific state of this task */ struct Thread thread; unsigned long task_entry; struct PCB *next; }tPCB;//pcb結構體定義 void my_schedule(void);
修改mymain.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); void __init my_start_kernel(void) //mykernel內核代碼入口 { int pid = 0; int i; /* 初始化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其他進程 */ 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]; } /* 用task[0]開始0號進程 */ 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) /* input c or d mean %ecx/%edx*/ ); } 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文件如下:
/*
* 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 1
if(time_count%1000 == 0 && my_need_sched != 1)
{
printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
my_need_sched = 1;
}
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;
prev = my_current_task;
if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
{
my_current_task = next;
printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
/* 進程切換 */
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)
);
}
return;
}
重新make編譯後qemu窗口查看,得到如下結果:
遇到問題
1.make編譯出錯
2.重新啟動後內核啟動效果沒有變化
解決方法
1.在mykernel目錄下修改代碼
2.換了一組新的內核代碼,最開始使用的代碼切換進程過程不完成
操作系統工作流程