geekos

GeekOS是一個基於X86架構的PC上運行的微作業系統核心,由美國馬理蘭大學的教師開發,主要用於作業系統課程設計,目的是使學生能夠實際動手參與到一個作業系統的開發工作中。出於教學目的,這個系統核心設計簡單,卻又兼備實用性,它可以運行在真正的X86 PC硬體平台。

GeekOS教學作業系統概論

作業系統是管理系統軟,硬體資源,控制程式運行,改善人機界面,提供各種服務,合理組織計算機工作流程和為用戶有效使用計算機提供良好運行環境的系統軟體,它為用戶使用計算機提供一個方便,靈活,安全,可靠的工作環境,也是其他套用軟體賴以存在的基礎.作業系統是計算機系統的重要組成部分,作業系統課程是計算機教育的必修課程,作為計算機專業的核心課程,不但高校計算機相關專業的學生必須學習作業系統,從事計算機行業的從業人員也需要深入了解它.
計算機作業系統課程是理論性和實踐性都較強的課程,具有概念多,抽象,涉及面廣的特點.在設定作業系統課程教學要求時,教師就要考慮對學生作出什麼樣的要求.是純理論的對書本習題和概念作出解答就可以了呢,還是要求學生能動手參與實踐.實踐也有不同程度的要求,是單純的實現作業系統的某些算法呢,還是實際編寫或修改作業系統功能模組.由於實踐環境的限制,許多高校目前都偏重對理論知識的要求,注重基本理論知識的掌握和一些典型算法的實踐(一般選擇UNIX或Linux作為實驗環境,要求學生用C語言編程實現簡單的進程創建,進程調度等算法),所以學生基本沒有機會去了解,實踐作業系統的內部結構和實現技術.實踐證明,要真正學好作業系統原理和設計技術,最好的方法就是讓學生參與到作業系統的開發工作中.因此,越來越多的高校在開設作業系統理論課程的同時,會要求學生對現有作業系統進行功能改進或再開發,以增加學生對作業系統核心技術的實踐,真正做到理論與實踐相結合.
那么,能用作學生作業系統課程實踐的平台有哪些呢 大家一般很容易想到使用現有的商業作業系統和開放原始碼的作業系統,也有很多這樣的作業系統可供學生選擇,比較流行的有Linux,Minix等.雖然也有一些學校確實採用這些作業系統作為實踐平台,但採用這些
作業系統存在的缺點也是不容忽視的:這些作業系統一般都結構龐大,過於複雜,學生在短時間內很難理解,而且這些作業系統幾乎已經實現了所有的功能(進程管理,存儲器管理,檔案系統等),不需要學生自行設計或實現一些子系統,因此從教學實踐的角度講,價值不高.最好的方法不是選擇一個完整的,實用的,龐大的商業作業系統,而是選擇一個既具備基本作業系統核心功能,與實際使用的作業系統比較接近,但又易於理解,規模較小的作業系統作為教學平台,在這個教學平台上,學生可以修改和擴充基本系統以實現更多功能,這種作業系統稱為教學作業系統.
當我們決定選用教學作業系統作為我們的作業系統課程實踐平台後,剩下的工作就是在現有的多種教學作業系統中選擇一種.教學作業系統有兩大類,一類是針對RISC結構MIPS處理器的,另外一類是針對CISC結構的Intel IA-32(或X86)通用處理器的.這樣分類是因為:處理器是作業系統運行的硬體環境中最重要的部分.
針對RISC結構MIPS處理器的教學作業系統有Nachos(Not Another Completely Heuristic Operating System)和OS/161.其中Nachos是建立在軟體模擬的虛擬機之上的教學作業系統,採用MIPS R2/3000的指令集,能模擬主存,中斷,網路以及磁碟系統等所必須的硬體系統,美國加州大學伯克利分校多次採用該作業系統作為課程設計平台.OS/161是運行在與作業系統無關的System/161模擬器上的,作業系統代碼是MIPS對應的機器代碼.但無論是Nachos還是OS/161,若學生使用Windows或Linux 開發環境的話,都需要使用交叉編譯器才能把代碼編譯成MIPS相應的機器代碼.
Minix和GeekOS是針對CISC結構的Intel IA-32 (或X86)通用處理器的.其中,Minix是Andrew S. Tanenbaum(AST)於1987年開發的,目前主要有1.5 版和2.0 版兩個版本在使用.Minix系統是免費的,可以從許多FTP 上下載,但Minix是一個包括了虛擬記憶體管理,檔案系統,設備驅動程式,網路和用戶態程式等的比較完整的作業系統,它由兩萬多行代碼組成,對於教學有點過於龐大和複雜,而且由於它已經實現了作業系統的全部基本功能,沒有留下合適的練習讓學生自己完成.
大家知道,最通用的處理器是CISC結構的Intel IA-32 (或X86)通用處理器,所以選用針對該結構的教學作業系統是比較合適的,我們選用GeekOS作為作業系統課程設計平台主要原因還有:它是一個用C語言開發的作業系統,學生可以在Linux或UNIX環境下對其進行功能擴充,也可以在Windows下使用Cygwin工具進行開發,且其針對進程,檔案系統,存儲管理等作業系統核心內容分別設計了7個難度逐漸增加的項目供教師選擇.我們將在後面的章節中詳細為大家介紹GeekOS教學作業系統.

GeekOS教學作業系統

一. GeekOS概述
GeekOS是一個基於X86架構的PC上運行的微作業系統核心,由美國馬理蘭大學的教師開發,主要用於作業系統課程設計,目的是使學生能夠實際動手參與到一個作業系統的開發工作中.出於教學目的,這個系統核心設計簡單,卻又兼備實用性,它可以運行在真正的X86 PC硬體平台.作為一個課程設計平台,GeekOS由一個基本的作業系統核心作為基礎,提供了作業系統與硬體之間的所有必備接口,實現了系統引導,實模式到保護模式的轉換,中斷調用及異常處理,基於段式的記憶體管理,FIFO進程調度算法以及核心進程,基本的輸入輸出(鍵盤作為輸入設備,顯示器作為輸出設備),以及一個用於存放用戶程式的唯讀檔案系統PFAT.
二. GeekOS的存儲器管理
GeekOS核心有兩種存儲器分配方式,分頁分配方式和堆分配方式.
1.分頁分配方式
系統中所有存儲器都分成大小相等的塊,稱作頁.在X86系統中,頁的大小是4KB.若在GeekOS中增加了支持虛擬存儲器的功能,頁也可以是虛擬存儲空間的存儲單元.在不支持虛存的系統中,頁也可以看作是一個固定大小的存儲塊,頁的分配和回收用函式Alloc_Page()和Free_Page(),這兩個函式的定義在頭檔案中.在GeekOS中每一頁都是一個Page結構:
struct Page {
unsigned flags; /* 頁狀態 */
DEFINE_LINK(Page_List, Page); /* Page_List頁鍊表指針*/
int clock;
ulong_t vaddr; /* 頁映射到的用戶空間虛擬地址 */
pte_t *entry; /* 指向頁表中本頁的頁表項*/
};
其中DEFINE_LINK是在list.h檔案中的宏定義,定義了指向鍊表節點的指針.GeekOS使用宏定義鍊表結構及操作,具體代碼請參考list.h檔案.系統全局頁鍊表struct Page_List g_pageList記錄記憶體所有頁的Page結構,其中flags標記為PAGE_AVAIL的頁為空閒頁,s_freeList記錄系統所有的空閒頁.
2.堆分配方式
堆分配提供不同大小存儲塊的分配,使用函式Malloc()和Free()進行存儲塊的分配和回收.
3.系統初始化記憶體布局
系統初始化時由Init_Mem函式將系統記憶體劃分為核心空間,可用空間等若干部分,如圖1-1所示:其中記憶體空洞是系統設計時留作其他功能使用的,屬保留區域.核心堆是一塊用於動態分配和回收的記憶體,系統使用bget,brel,bpool三個函式管理這塊空間,堆分配方式中的Malloc函式與Free函式就是通過調用這些函式實現動態分配和回收.可用空閒記憶體用於分配其他的系統數據.
圖1-1 系統初始化記憶體布局
三. GeekOS支持的設備
1.文本顯示器
文本顯示器支持顯示文本信息,GeekOS中的顯示驅動僅能支持VT100和ANSI的一個子集,且不含方向移動和字元特性設定等功能.實現文本顯示的函式在頭檔案中定義.
用戶在編程時經常會用到的一個函式是print(),它是標準C語言函式Printf()的子集,功能是將文本信息輸出到顯示器.其他還有一些輸出函式,如Put_Char()和Put_Buf(),使用這兩個函式分別可以輸出單個字元和字元串.
2.鍵盤
鍵盤設備驅動程式提供了一系列高級接口以使用鍵盤.用戶需要注意的是鍵盤事件的邏輯關係:用戶按鍵引發鍵盤中斷,鍵盤中斷讀取用戶按鍵並將鍵碼放到鍵盤緩衝區s_queue中,而用戶進程則將緩衝區的鍵盤碼讀出來作進一步處理.
若用戶進程需要從鍵盤輸入信息,可調用Wait_For_Key()函式,該函式首先檢查鍵盤緩衝區是否有按鍵,如果有,就讀取一個鍵碼,如果此時鍵盤緩衝區中沒有按鍵,就將進程放入鍵盤事件等待佇列s_waitQueue,由於用戶的按鍵操作觸發了鍵盤中斷,鍵盤中斷處理函式Keyboard_Interrupt_Handler就會讀取用戶按鍵,將低級鍵掃描碼轉換為含ASCII字元的高級代碼,並刷新鍵盤緩衝區,最後喚醒等待按鍵的進程繼續運行.若用戶按下Shift,Control,Alt等鍵時,也能同樣處理.鍵盤處理程式的代碼在頭檔案中,詳見第8章項目設計0.
3.系統時鐘
GeekOS中用戶一般不直接使用任何時鐘服務,系統時鐘主要用於時鐘中斷,一般用於保證所有的執行緒都有機會占用CPU,即執行緒運行一段時間後會發生時鐘中斷,調度程式就選擇另外的執行緒運行.
4.塊設備:軟碟和IDE硬碟
塊設備是指按固定大小的塊(扇區)存取信息的存儲設備,塊設備一般作為檔案系統的基本存儲設備,檔案系統會在物理塊存儲的基礎上創建檔案,目錄等以方便操作.不同塊設備的扇區大小不完全一樣,但GeekOS系統中假設所有塊設備的扇區大小都一樣——512個位元組,並用宏SECTOR_SIZE常量進行了定義.
GeekOS支持兩種塊設備:軟碟和IDE硬碟,系統用名字fd0表示第一個軟碟機,ide0表示第一個IDE硬碟分區,ide1表示第二個IDE硬碟分區.塊設備的分區信息用核心的BLOCK_DEVICE數據結構表示,用戶要使用某個設備的時候只要調用函式Open_Block_Device(),函式參數就是用戶要使用的設備名.打開設備後,用戶就可以分別調用Block_Read()和Block_Write()來讀,寫設備指定扇區的信息.GeekOS系統中塊設備操作處理過程如圖1-2所示.
圖1-2 GeekOS系統塊設備操作處理流程
GeekOS系統中,每個塊設備都用一個Block_Device結構記錄:
struct Block_Device {
char name[BLOCKDEV_MAX_NAME_LEN]; // 塊設備名稱,如"ide0"
struct Block_Device_Ops *ops; // 指向塊設備操作函式指針列表的指針
int unit; // 單位磁碟塊的大小,如一個硬碟磁碟塊為4KB
bool inUse; // 設備是否在使用
void *driverData; // 實體塊設備信息
struct Thread_Queue *waitQueue; // 等待訪問塊設備的進程的佇列
struct Block_Request_List *requestQueue; // 塊設備訪問請求佇列
DEFINE_LINK(Block_Device_List, Block_Device); // 塊設備鍊表指針
};
其中Block_Device_Ops結構定義:
struct Block_Device_Ops {
int (*Open)(struct Block_Device *dev); // 打開塊設備
int (*Close)(struct Block_Device *dev); // 關閉塊設備
int (*Get_Num_Blocks)(struct Block_Device *dev); //塊號
};
GeekOS設計項目中,系統主要將塊設備用作GeekOS檔案系統的物理載體.在系統初始化時分別將檢測到的軟碟,硬碟作為塊設備註冊到系統塊設備列表,在註冊塊設備時,系統將其Block_Device結構初始化,並添加到s_deviceList設備鍊表.這個鍊表由各個設備的Block_Device結構指針組成.系統提供了一系列塊設備操作函式:Open_Block_Device,Block_Read,Block_Write,Close_Block_Device等.標準的塊設備使用過程順序是:首先調用Open_Block_Device函式打開s_deviceList設備列表中指定的設備,然後調用Block_Read或Block_Write函式執行讀寫操作,完成操作後調用Close_Block_Device關閉塊設備.實現塊設備存取的函式在頭檔案中進行定義.
四.GeekOS的中斷和執行緒
1.中斷
中斷是用來向CPU通知重要事件的,中斷的重要特性是:引發CPU控制權的轉移.中斷髮生後,執行緒會暫停執行,而轉去執行相應的中斷處理程式,中斷處理程式實際上是一個C語言程式.中斷處理結束後,控制權返回給發生中斷的執行緒,大多數情況下,執行緒會像沒有發生中斷一樣繼續執行.但由於中斷可能引發執行緒交換,所以也可能導致共享的核心數據結構被修改等.
下面是一些不同類型的中斷及中斷處理方法.
Exception(異常):若當前運行的執行緒執行了非法操作,則發生異常.異常是一種同步中斷,因為異常的發生是可預知的.這類中斷的例子有執行非法指令,被0整除等.因為這種異常無法恢復,所以通常都只能採取撤銷父執行緒的方式處理.
Faults(故障):故障和異常一樣屬於同步中斷.和異常不同的是,故障通常是可恢復的,核心通過執行一系列操作,去除引起故障的條件,然後使發生故障的執行緒繼續執行即可.缺頁中斷就是故障的一種,缺頁中斷是指CPU要訪問的數據或代碼還沒有調入記憶體時發生的中斷,當核心找到要訪問的內容並調入記憶體空間時,發生中斷的執行緒就可以繼續執行了.
Hardware interrupts(硬體中斷):外設用硬體中斷將某些事件通知CPU.硬體中斷是不可預知的,所以是異步中斷,也就是說,異步中斷隨時會發生.但有時系統可能無法立即處理異步中斷,在這種情況下,系統會暫時禁止中斷直到系統能處理中斷時再處理中斷.時鐘中斷就是一種硬體中斷.
Software interrupt(軟體中斷):用戶態進程用於發出信號表示它需要系統核心的干涉.GeekOS中僅使用一種軟體中斷——系統調用.執行緒用系統調用向核心發出服務請求,如執行緒要打開檔案,執行輸入,輸出操作,創建新執行緒等.
2.中斷處理函式
GeekOS中斷調用的實現主要涉及到兩個數據結構:中斷描述符表s_IDT與中斷處理函式表g_interruptTable.s_IDT在核心中靜態分配,每個描述符指向lowlevel.asm檔案中定義的中斷向量宏定義程式段,這些程式段定義了256箇中斷向量的調用.其中,0~17號中斷為Intel CPU的CPU中斷向量,18~225號為用戶自定義的中斷向量.GeekOS將大多數中斷處理函式初始化為函式Dummy_Interrupt_Handler,該函式在中斷時顯示當時保存的計算機各暫存器狀態.12~13號CPU中斷處理函式為GPF_Handler,系統調用使用Syscall_Handler中斷處理函式,鍵盤,計時器等硬體中斷也有各自的外部中斷處理函式.
3.執行緒
在支持執行緒的系統中,允許多個任務分時共享CPU.GeekOS中每個執行緒都是一個Kenerl_Thread對象,在中定義.系統有一個調度程式(scheduler)用於選擇執行緒運行.任意時刻系統只能有一個執行緒在運行,這個運行執行緒就稱作當前執行緒(current thread),在全局變數g_currentThread中有一個指針指向當前正在運行進程的Kenerl_Thread對象.如果執行緒已經準備好可以運行了,但不是正在運行的,就放入準備運行佇列(run queue).執行緒若在等待某件事情發生則放入等待佇列(wait queue).無論是等待佇列還是準備運行佇列的執行緒都用數據結構Thread_Queue記錄,Thread_Queue是Kenerl_Thread對象中的一個鍊表結構.
系統中一些執行緒完全運行於核態,這類執行緒稱為系統執行緒.系統執行緒一般用於完成服務請求,如reaper執行緒用於釋放已經執行結束的執行緒所占用的資源;軟碟和IDE接口的磁碟各使用一個系統執行緒用於I/O請求,I/O操作並將操作結果傳輸給請求執行緒.
進程不同於系統執行緒,進程大多情況下在用戶態執行,我們對進程應該很熟悉了,當我們在Linux或Windows下運行一個程式時,系統都會為程式創建相應的進程.每個進程都占用各自的存儲空間,檔案,信號量等資源.GeekOS中的進程其實就是一個簡單的執行緒,每個進程有一個特殊的數據結構User_Context,由於GeekOS中的進程是能運行於用戶態的普通執行緒,所以也稱為用戶執行緒.由於進程在用戶態運行,當發生中斷時,系統會切換到核態進行中斷處理,當中斷處理結束後,系統再返回到用戶態繼續運行進程.
4.執行緒同步
GeekOS提供了高級執行緒同步機制:互斥信號量(mutexes)和條件變數(condition variables),它們的定義在頭檔案中.需要注意的是互斥信號量和條件變數只能用於執行緒同步,異步中斷處理不能訪問互斥信號量或條件變數.
互斥信號量用於保護臨界區的互斥訪問,即同一時刻僅允許一個執行緒訪問臨界區,如果一個執行緒要訪問一個互斥信號量,而此時已經有另外一個執行緒已經在使用該互斥信號量,那該執行緒只能被阻塞,直到該互斥信號量可以被訪問.下面是一個例子.
#include
struct Mutex lock;
struct Node_List nodeList;
void Add_Node(struct Node *node){
Mutex_Lock(&lock);
Add_To_Back_of_Node_List(&nodeList , node);
Mutex_Unlock(&lock);
}
每個條件變數代表一個執行緒可以等待的條件,每個條件變數與一個互斥信號量相關,當檢測或修改與條件變數有關的程式段時,必須保持互斥訪問.下面是一個條件變數訪問的例子.
#include
struct Mutex lock;
struct Condition nodeAvail;
struct Node_List nodeList;
void Add_Node(struct Node *node){
Mutex_Lock(&lock);
Add_To_Back_of_Node_List(&nodeList , node);
Cond_Broadcast(&nodeAvail);
Mutex_Unlock(&lock);
}
struct Node *Wait_For_Node(void){
struct Node *node;
Mutex_Lock(&lock);
While (Is_Node_List_Empty(&nodeList)){
/*等待另外一個執行緒調用Add_Node()*/
Cond_Wait(&nodeAvail,&lock);
}
node = Remove_From_Front_of_Node_List(&nodeList);
Mutex_Unlock(&lock);
Return node;
}
5.執行緒和中斷的互動
理解中斷和執行緒的互動對擴充核心功能是極為重要的.GeekOS核心是允許搶占的,這就意味著執行緒切換可能隨時發生.在某個搶占點,由調度程式選擇哪個執行緒運行,一般情況下調度程式會選擇最高優先權的運行態執行緒運行.但執行緒切換還是會經常發生,如為防止某個執行緒長時間占用CPU,系統提供時鐘中斷.時鐘中斷會引發異步執行緒切換;其他硬體中斷,如軟碟訪問中斷,也會引發異步執行緒切換.因為執行緒切換或中斷處理會引起一些共享數據結構的修改,可能導致核心崩潰或其他不可預知的後果.好在系統可以通過禁止中斷使搶占暫時不可用.調用函式Disable_Interrupts()就可以禁止中斷了(函式原型定義在中),一旦調用這個函式,處理器將忽略所有外部硬體中斷,其他執行緒和中斷處理程式都不能運行,就可以保證當前執行緒對CPU的控制權,若要允許搶占,只要調用函式Enable_Interrupts()開中斷就可以了.系統在某些情況下也必須禁止中斷,如執行原子操作(構成操作的一系列指令需要當作一個整體執行,執行過程不能中斷);修改調度程式使用的數據結構時也必須禁止中斷,如將當前執行緒放入等待佇列時等.下面有一個需要禁止中斷的例程.
/*執行緒等待*/
Disable_Interrupt();
While (!eventHasOccurred){
Wait(&waitQueue);
}
Enable_Interrupt();
在這個例子中,執行緒在等待一個異步事件的發生,在該事件發生前,執行緒都將自我阻塞在等待佇列.當等待的事件發生後,該事件的中斷處理程式將eventHasOccurred標誌改為true,並將該進程從阻塞佇列移到運行佇列.用戶可以假設一下,如果在上面的代碼中,沒有禁止中斷會怎么樣.首先,事件的中斷處理可能在檢測eventHasOccurred標誌和將執行緒放入等待佇列的中間發生,那就意味著無論等待的事件是否已經發生,執行緒都將永遠等待.第二種可能性是,線上程放入等待佇列的過程中,可能會發生執行緒切換,中斷處理程式會將當前運行的進程放入運行佇列,而等待佇列處於修改(不一致)狀態.此時,若運行的代碼需要訪問等待佇列,將引起系統崩潰.幸運的是,在GeekOS中,所有需要禁止中斷才能運行的函式中都有斷點,如果沒有禁止中斷就調用這些函式,系統會立即發出提示.所以,在函式代碼中,我們經常可以看到這樣的語句:KASSERT(!Interrupts_Enabled()).這個語句就表示若要繼續執行下面的代碼,請先禁止中斷.剛開始寫核心代碼的時候,用戶可能不容易知道運行哪些代碼段是需要禁止中斷的,慢慢的用戶就能熟悉了.
五.GeekOS系統引導和初始化
GeekOS系統的開發是基於真正的硬體環境的,因此完全可以在一台x86 PC上安裝並運行,但還是推薦大家在PC模擬器bochs上運行,因為軟體模擬硬體的可靠性比真實硬體高得多,不會因為硬體故障而導致系統崩潰,也便於核心程式的調試,不必要經常關閉和重新啟動計算機系統.Bochs是用C++開發的可移植的IA-32(x86)PC模擬器,能夠在大部分常見作業系統平台上運行,它包括了對Intel x86 CPU,通用I/O設備和定製BIOS等的模擬,當前是由Bochs項目組進行維護.在Bochs模擬器上也能夠運行大部分的作業系統,Linux,Windows,DOS等.Bochs可以從網站http://bochs.sourceforge. net下載,詳細的Bochs內容將在第2章介紹.用戶首先啟動Bochs模擬器,主機作業系統載入Bochs程式到主機的記憶體中.BIOS載入完成後,在Bochs中的操作就會像在真的計算機上操作一樣.
1.GeekOS系統引導
GeekOS核心編譯後,在build目錄下會生成一個軟碟鏡像檔案fd.img.在Bochs開始運行系統後,會自動檢測啟動設備.因為軟碟首扇區最後一個字在編譯時是寫入55AA數據,而Bochs被配置為從軟碟啟動,這樣Bochs得以成功地檢測到GeekOS的啟動軟碟.之後Bochs就會像一台真正的計算機一樣,首先導入軟碟的首扇區數據到從記憶體地址0x7c00開始的一塊記憶體區,之後跳轉到這個地址,開始執行這段首扇區內的程式代碼.首扇區內的代碼是由位於/src/geekos目錄中的fd_boot.asm編譯生成的引導程式.這段彙編程式完成搜尋並裝載軟碟中的GeekOS核心二進制檔案的功能.在裝載完畢後,裝載程式執行段間跳轉,轉入程式Setup(/src/geekos目錄中的setup.asm編譯生成)繼續執行.
Setup程式完成裝載臨時GDT,IDT描述符,打開A20地址線,初始化PIC中斷控制器,最後由實模式跳入保護模式.完成了實模式向保護模式的轉換之後,Setup跳轉到核心ENTRY_POINT入口點.至此,GeekOS的引導過程結束,核心初始化過程開始.
需要說明的一點是Setup除了跳轉到保護模式之外,還作了一些核心初始化的準備工作:通過調用BIOS中斷重置軟碟驅動,檢測計算機可用記憶體;之後Setup在堆疊創建一個Boot_Info結構,並將檢測到的記憶體大小存放到這個數據結構中保存.
2.GeekOS系統初始化
GeekOS的核心入口點ENTRY_POINT指向的是核心Main函式的函式入口,在編譯時完成對ENTRY_POINT的初始化.Main函式在src/src/geekos/main.c中實現.Main函式通過調用核心各模組的初始化函式來完成系統核心的初始化,這些函式絕大部分都由原始的GeekOS核心提供,不需要自己編制,這大大減輕了GeekOS的項目開發難度.
下面簡要介紹這些核心各模組的初始化函式,不同項目的初始化函式基本相同.
Init_BSS()函式:初始化核心的BSS段.
Init_Screen()函式:初始化文本顯示器,這之後核心可以使用Print函式輸出文本字元信息.
Init_Mem(bootInfo)函式:這裡接收的參數bootInfo就是之前在Setup程式中初始化的Boot_Info結構,這個結構保存了Setup在實模式檢測到的記憶體大小數據.Init_Mem函式根據這個值來初始化系統記憶體,首先將Setup中使用的臨時GDT複製到核心,之後再重新劃分記憶體使用空間,包括劃分核心程式所占記憶體,核心堆記憶體,系統空閒記憶體,跳過記憶體空洞等;系統接著建立了一個系統空閒記憶體鍊表.
Init_CRC32()函式:初始化一個CRC校驗表,這個校驗表用於記憶體分頁管理系統的實現,在項目4中要用到.
Init_TSS()函式:初始化TSS段描述符及TSS段,TSS段的空間是核心靜態分配的,並全部初始化為零值.
Init_Interrupts()函式:初始化中斷向量表以及中斷,異常處理的默認函式,之後允許中斷.從這裡開始核心就可以回響中斷了.
Init_VM(bootInfo)函式:初始化記憶體分頁功能,這個函式用於分頁記憶體管理系統的實現,在項目4中要用到.
Init_Scheduler()函式:初始化核心主進程Main,創建用於管理進程的核心Idle進程和Repair進程,初始化全局進程列表.
Init_Traps()函式:初始化12號CPU中斷異常處理函式,13號CPU中斷異常處理函式以及系統調用中斷處理函式.GeekOS系統調用使用0x90號中斷向量.
Init_Timer()函式:初始化計時器,包括制定計時器中斷周期時間片,初始化時鐘中斷處理函式;之前在初始化PIC外部中斷控制器時禁止了所有的外部中斷,這裡最後將時鐘中斷重新開啟.需要注意的是時鐘中斷處理函式,這個函式負責系統進程的調度.
Init_Keyboard()函式:初始化鍵盤輸入,包括初始化鍵盤緩衝區佇列,初始化鍵盤中斷處理函式,最後開啟鍵盤中斷.
Init_DMA()函式:初始化DMA控制器.
Init_Floppy()函式:初始化軟碟驅動.GeekOS默認提供一個軟碟機與兩個IDE接口硬碟.這裡函式添加了用於處理軟碟機中斷的中斷處理函式,以及核心進程Floppy_Request_Thread專門用於處理訪問軟碟機的請求.
Init_IDE()函式:初始化塊設備驅動,這之後系統識別出掛載的硬碟,並添加處理硬碟訪問請求的核心進程IDE_Request_Thread.出於系統運行效率考慮,GeekOS沒有為IDE中斷編制專門的中斷處理函式,而是採用直接讀取IDE控制器狀態字的形式判斷IDE工作的完成狀態.
Init_PFAT()函式:初始化唯讀檔案系統PFAT.
Init_GOSFS()函式:初始化檔案系統GOSFS,在項目5中要用到.
Mount_Root_Filesystem()函式:掛載默認的檔案系統PFAT系統.在後來的項目設計5中,用戶會看到GeekOS最終掛載了PFAT與GOSFS兩個檔案系統.
至此系統初始化完畢,可以開始運行用戶程式了.
1.2.6 GeekOS系統原始碼結構和設計項目
GeekOS作業系統源檔案可以從http://geekos.sourceforge. n et下載,最新版本是2005年4月的GeekOS-0.3.0.從下載壓縮檔解壓出來後的GeekOS目錄結構如圖1-3所示.
在doc目錄下檔案hacking.pdf和index.htm,是GeekOS系統的參考文檔.Scripts目錄下有startProject和removeEmptyConflicts兩個腳本檔案.GeekOS系統的源檔案在src目錄下,分7個項目:project0,project1, project2,project3, project4,project5和project6.每個項目的檔案結構都類似,以project0為例,結構如圖1-4所示.
圖1-3 GeekOS系統主目錄 圖1-4 項目檔案結構圖
在build資料夾中,包含系統編譯後的執行檔的檔案,軟碟鏡像fd.img(project1等項目中還包含有磁碟鏡像diskc.img),makefile項目管理檔案.在Include資料夾中有geekoslibc兩個子目錄,在geekos子目錄中有kthread.h,keyboard.h等頭檔案,在libc中包含有GeekOS支持的C語言標準函式string.h頭檔案.在scripts資料夾中是項目編譯時要用到的一些腳本檔案.src資料夾中存放系統核心原始碼,用戶修改GeekOS系統時要修改的原始碼如main.c等都位於這個目錄中.在User子目錄中一般是用來存放用戶的測試檔案,在tools子目錄中的代碼是用來建立pfat測試檔案系統的.
在提供的GeekOS核心系統的基礎上,為學生設計了7個由易到難的設計項目用於GeekOS的改進.這些項目分別涵蓋了作業系統核心的各個基本模組:系統啟動,進程管理,存儲管理,檔案系統,訪問控制以及進程間網路通信.7個項目都規定了改進的目標,並提供了一些設計指導性的意見,但沒有提供原始碼,所以學生首先必須熟悉GeekOS的基本工作原理,才能開展各個項目的設計與實現.
項目0:主要是讓學生熟悉GeekOS的編譯,運行過程,了解計算機系統的啟動原理.項目0要求實現一個核心進程,功能是實現從鍵盤接收一個按鍵,並在螢幕上顯示.
項目1:主要讓學生熟悉可執行連結檔案(ELF檔案)的結構,並學會載入和運行執行檔.項目要求學生熟悉ELF檔案格式,並編寫代碼對ELF檔案進行分析,並將分析結果傳送給載入器.
項目2:要求學生實現對用戶態進程的支持.在項目2實現之前,GeekOS一直使用核心進程.對用戶態進程執行的支持包括用戶態進程結構的初始化,用戶進程空間的初始化,用戶進程切換和用戶程式導入等.該項目中,存儲分配依然使用分段分配方式.實現項目2後,用戶就可以使用GeekOS提供的命令行解釋器Shell運行一些命令來執行PFAT檔案系統內的用戶測試程式.
項目3:要求學生改進GeekOS的調度程式,實現基於4級反饋佇列的調度算法(初始GeekOS系統僅提供了FIFO進程調度算法),並實現信號量,支持進程間通信.
項目4:要求學生實現分頁虛存管理,以替代在項目1和項目2中採用的分段存儲管理.實現分頁虛存管理後.系統在記憶體不夠的情況下就可以將部分頁調到硬碟,以釋放記憶體實現虛擬存儲技術.
項目5:要求實現GOSFS檔案系統.由於GeekOS使用了虛擬檔案系統,可以載入不同的檔案系統,而系統默認載入的是PFAT唯讀檔案系統.在這個項目中,需要實現一個多極目錄的,可讀寫的檔案系統.
項目6:要求為檔案系統增加訪問控制列表,並使用匿名半雙工管道實現進程間通信.
在某種程度上,GeekOS就是一個簡單的C程式,有功能函式,執行緒,記憶體分配等.但與在裝有Linux或Windows作業系統中運行的C語言程式不同,一般C語言程式 是運行在用戶態的,而GeekOS是運行在核態的(也稱系統態).在核態運行的程式可 以完全控制計算機的CPU,記憶體和外部設備,所以編寫運行於核態的程式時有一些需 要特別注意的問題,修改GeekOS核心代碼時要特別注意GeekOS核心運行環境的一些限制.
1.有限庫函式
因為作業系統是計算機中最底層的軟體,核心使用的所有功能函式必須能在核心執行.這與用戶程式不同,用戶程式可與一系列包含常用函式的標準庫連線.
GeekOS中唯一能使用的標準C語言庫函式是字元串函式的一個子集(strcpy(),memcpy()函式)和snprintf()函式,這些函式的原型定義在頭檔案中.除標準C語言庫函式外,GeekOS核心還有一些與C語言庫函式類似的函式,如Print()函式(函式原型定義在中),是標準C函式printf()函式的功能子集;Malloc()和Free()函式則相當於標準C的malloc()和free()函式(函式原型定義在中).
2.有限棧空間
GeekOS核心中的每個執行緒可以使用大小為4KB的棧.如果某個執行緒堆疊溢出將導致系統核心崩潰.因此,在編程時應謹慎使用棧空間:
不要用棧分配大的數據結構,使用棧時儘量使用堆疊分配函式Malloc()和Free();
程式中不要使用遞歸,避免函式深層次的調用.
3.存儲器保護限制
一旦系統核心開始運行,沒有任何存儲器保護,核心的每次存儲器訪問都是對物理記憶體單元的訪問,即使是引用空指針系統也無法捕獲(trapped).因此,系統在核態運行比在用戶態運行時指針引用更容易出問題.要調試運行於核態的程式是比較難的,所以用戶必須仔細檢查編寫的代碼,確保沒有記憶體訪問錯誤.
如果用戶為GeekOS核心增加虛擬記憶體管理功能,那核心將獲得較好的記憶體訪問保護,但用戶在編寫核心代碼時仍然要仔細檢查,避免一些不易調試的錯誤發生.
4.異步中斷
當有重要事件發生的時候,許多硬體設備都用中斷來通知CPU:如定時器定時到,I/O請求完成等.中斷髮生時,控制權異步傳送給中斷處理程式,當中斷處理結束時,控制返回到中斷點繼續執行.值得注意的是,中斷處理可能會引起執行緒交換,這就意味著在控制返回到被中斷的執行緒前,系統可能會執行其他執行緒.
如果用戶遵守上述的限制,在核心模式下編程會相對容易.不僅如此,為使用戶更順利地深入核心編程,還建議用戶養成下列編程習慣.
使用斷點.
在頭檔案中,有一個宏定義KASSERT(),參數是一個布爾表達式.如果表達式的值是false時,就列印出一條信息,並暫停核心運行.舉例如下:
void My_Function(struct Thread_Queue *queue)
{
KASSERT(!Interrupts_Enabled()); /* 必須禁止中斷*/
KASSERT(queue != 0); /* 佇列不能為空 */
...
}
用戶應儘可能多地用斷點檢查函式執行的前提條件,後繼條件和數據結構的特性等.使用斷點有兩個好處:第一,若程式執行出錯,斷點可以在核心崩潰前,精確快速的幫助程式設計師定位程式中的出錯代碼;第二,斷點還可以幫助程式設計師檢測代碼正確性.
使用Print語句.
在頭檔案定義了Print()函式,它支持標準C語言函式printf()的大部分功能.Print是最常用也是最有效的用於調試核心代碼的語句,因為調試時,用戶採用的策略都是先作假設,然後再收集證據來支持或反駁假設.
儘可能提前,經常測試.
與用戶程式相比,核心程式需要更好的可擴展性,所以核心代碼常常分成一個個小的模組開發和測試.在系統開發過程中,每一個模組應儘早地,單獨地進行測試,提高系統可靠性,當核心開發達到一個穩態時,最好採用版本控制,建議用戶使用CVS軟體來存儲核心代碼.

相關詞條

相關搜尋

熱門詞條

聯絡我們