標籤: 暫無標籤

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

1 geekos -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教學操作系統.

2 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); "

  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-2GeekOS系統塊設備操作處理流程

  在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)){

  "

  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.6GeekOS系統源代碼結構和設計項目

  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-3GeekOS系統主目錄 圖1-4 項目文件結構圖

  在build文件夾中,包含系統編譯后的可執行文件的文件,軟盤鏡像fd.img(project1等項目中還包含有磁碟鏡像diskc.img),makefile項目管理文件.在Include文件夾中有geekos和libc兩個子目錄,在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軟體來存儲內核代碼.

上一篇[geninterrupt]    下一篇 [GDSN]

相關評論

同義詞:暫無同義詞