作者:湯海京 前言及緒論 《基於面向對象操作系統開發平台(OSKit)的分析與程序設計》是我們奉獻給讀者的一個新專欄。本專欄的作者湯海京將向大家系統地介紹OSKit的線程機制,主要論述了三大部分內容,它們是:線程通訊,線程初始化和線程調度。希望讀者能在最短的時間內了解OSKit。本文是第一篇《前言及緒論》。 前言 如果說自由軟件的出現是一個偶然的話,那麼,席卷全球的Linux熱潮則是一個奇跡,它正以勢不可擋的趨勢迅猛發展,其前途不可限量。 Linux內核源代碼的開放給希望深入操作系統內部世界的人們提供了可能,但隨之而來的問題是,當我們要開發自己的操作系統時,由誰來讀系統的kernel部分呢。對這部分的處理從邏輯上分析不外乎三種方式:全部保留、對其進行裁減、全部推倒重來。很顯然,最後一種方法是不可能的,而如果我們采用的是第一種方法,其結果當然一定可以滿足我們的要求,但是,最後編譯出來的核心將十分的龐大,尤其是對嵌入式操作系統的開發者來說,是不能忍受的,所以,大多數開發者采用的第二條路。 但是,第二條路也非平坦的大道,道理很簡單,你要想對kernel進行裁減,首先你應該將全部的源代碼閱讀一遍,並且將其中的相關性理順,然後才能談到裁減,所以工作量也十分的龐大。 然而,OSKit的出現改變了這一切,它使得我們不需要將精力集中在kernel源代碼的閱讀上,因為kernel部分的源程序已經由OSKit的開發人員替你分析過了,他們將源碼全部模塊化,並將所有模塊之間的相關性寫在了文檔之中,呈現在你的面前,這與你自己分析源代碼的結果是一樣的。 OSKit最本質的東西和Linux一樣,體現在"自由"和"開放"的思想,"自由"意味著世界范圍內的知識共享,由於OSKit出現在Linux之後,其設計思想繼承了Linux的精髓,所以說它的出現並不完全是美國猶它大學計算機科學系FLUX研究組的功勞,而應該是"自由"的結果。"開放"則意味著OSKit對所有的人都敞開大門,在這種開放而自由的天地裡,你可以中分發揮自己的創造才能。 緒 論 1.1 簡介 OSKit是由美國猶它大學計算機科學系FLUX研究組編寫的一套模塊化部件和庫函數,用於架構操作系統內核、服務器以及其他的OS級軟件。我們設想一下,在一個操作系統的研發項目中,底層模塊的開發工作會占去大部分時間,並耗費掉開發人員的大部分精力。而OSKit的出現恰恰彌補了這個缺陷,其設計意圖是提供一套可重用的模塊,讓使用者避開復雜的底層,把精力集中在他們感興趣的問題上,也就是說,當開發人員拿到OSKit之後,便立刻擁有了一個完整而且安全的核心,使他們可以集中精力研發操作系統的高層次問題,如作業控制、虛存、IPC、文件系統、系統安全以及高級語言(如Java、Lisp或ML)等。這樣可以大大豐富操作系統的應用層,為用戶提供更多更好的服務,提高操作系統的運行效率,增強操作系統的安全性和穩定性,從而使你的操作系統更加具有魅力。 對於站在操作系統技術最前沿的多線程編程和成熟的作業控制系統,以及時下最流行的嵌入式操作系統,OSKit都提供了支持。通過對美國猶它大學計算機科學系FLUX研究組網站的追蹤,我們注意到OSKit的版本大約每三個月就更新一次,而且在每次發布的版本裡都有許多新的算法公布,還有許多老版本中的BUG被修改;這說明OSKit不但一直處於操作系統開發平台的最前沿,而且其自身也在不斷的完善。 通過對OSKit深入細致的分析與研究,我們發現猶他大學的開發人員們從一開始就確定了自己的目標,那就是對OSKit進行模塊化,仿佛Windows中的動態連接庫一樣,讓後來者即使不使用OSKit中的某一部分,仍然可以使用其余部分來完成他們的目標。這樣的設計思路十分靈活,為開發者和使用者都提供了便利。 1.2 安裝與配置OSKit 當我們從猶他大學得到OSkit的2000年五月版時,它是個壓縮包,使用tar在linux下解開後,就得到了源碼,這需要我們在linux下重新編譯。 目前,編譯OSKit需要以下的工具:GNU make、GNU CC (gcc) 2.95.2版。 注意:Redhat Linux 6.2中的GCC並不是2.95.2,如果要成功編譯OSKit,需要先將我們的GCC升級。當然,如果你使用的是Turbo linux 6.0的話,它提供的就是GCC 2.95.2。 在開始編譯OSKit之前,我們必須注意一點,那就是要對源碼進行一點改動,讓它支持實時操作,即將 /oskit20000505/threads/MakeFlage 中的: # OSKIT_CFLAGS + = -DPTHREAD_SCHED_EDF -DPTHREAD_REALTIME 中的注釋符號 # 去掉。 由於我們的環境是Linux,所以論文中的根目錄用"/"表示,而不是Windows中的"\"。由於我們是在intel 586上編譯,而我們的目標機要支持到intel 386,所以我們應該在OSKit的目錄下鍵入: ./configure ――host=i586 ――target=i386 此後鍵入make,編譯OSKit。當OSKit編譯好以後,你可以用"make install"命令來安裝它。在缺省的情況下,這些庫會被安裝到/usr/local/lib中,而頭文件會被安裝到/usr/local/include中,除非你在配置時使用了--prefix選項來指定它。所有的OSKit頭文件都安裝在oskit/子目錄中(例如/usr/local/include/oskit),不會和已經存在的任何頭文件沖突。即使是庫被安裝在主庫的目錄中(如/usr/local/lib),所有的庫文件都有前綴oskit_,這可以避免和其它不相關的庫混淆在一起。 1.3 使用OSKit 一開始,為了能讓大家盡快熟悉OSKit,猶他大學的研發人員們給出了許多很具代表性的例子供使用者進行試驗,我們也編寫了一個小型的操作系統,供學習研究之用。 OSKit中庫的設計是很經典的,開發者們花費了大量時間清楚地標出了每個庫與其它庫的依賴關系,這使得OSKit不但保留了操作系統的原汁原味,而且還把庫之間的相關性降低到了最低限度,使得使用者可以隨意增減其中的庫函數。實際上,在很多情況下,特別是在某些函數庫中,為了有效的使用OSKit,必須替換掉其中的一些函數或符號,以適應我們的執行環境。要重載某個庫中的函數或者符號,只要在你的核心或應用程序中再定義一遍就可以了,LINK程序會確保去使用你所定義的函數和符號。由於在Linux下的編譯器是靠讀取Makefile文件來確定編譯連接的對象以及規則,所以我們認為直接修改Makefile文件要比修改OSKit源碼更加方便而且安全,這與Windows下的工程文件十分相似。 如果使用OSKit所提供的模塊化部件和庫函數編寫出了自己的操作系統核心代碼,並且對在LINUX下用GCC編譯源碼有所了解的話,就可以借助OSKit為使用者提供的一整套核心制作命令和LINUX提供的GCC來生成自己的核心文件zImage,然後使用LINUX命令DD將核心寫入一張1.44MB的軟盤,重新啟動計算機,從軟盤引導就可進入我們自己編寫的操作系統了。 另外,考慮到用戶環境復雜多樣的實際情況,OSKit還提供了對許多種操作系統的支持,包括Linux、Mach 、BSD或者MS-DOS,當我們用GCC將源碼編譯成"filename.o"的格式之後,使用OSKit提供的不同的核心制作命令來生成不同操作系統下的核心,其具體實現是由mkbsdimage、mklinuximage和mkdosimage來完成的。這些腳本在OSKit的安裝和配置階段會根據你指定的環境自動安裝完畢。在每一個腳本運行的時候都有各自不同的參數。 1.4 OSKit 導航圖 OSKit所提供的功能被分成三類:接口、函數庫和部件庫 1.4.1 接口 OSKit的接口非常清晰,並且是面向對象的。這些接口全部被定義在C語言的頭文件中,我們分別對其加了必要的注釋,以方便使用者快速掌握OSKit。這些接口被OSKit的各個部件共享,從而體現了各部件之間的一致性。操作系統的開發人員既可以直接使用它,也可以在其上定義目標操作系統的接口結構。 1.4.2 函數庫 OSKit的函數庫以C語言中面向函數的方式提供基本的服務。一個庫中的函數對另一個庫中函數的依賴關系已經被降至最低的程度,如果這種依賴是不可避免的話,那他們已經被很清楚地標注出來,展現給了操作系統的開發人員。函數庫很少使用OSKit的面向對象的COM接口,取而代之的是在C的頭文件中定義自己的面向函數的接口。這種設計策略為操作系統開發人員提供了靈活性和極大的技術支持;要適應任何特殊的操作系統環境,底層的OSKit工具必須具有可擴展性。 1.4.3 部件庫 OSKit的部件庫使使用者能站在更高的層次來編程,即面向對象的"黑盒"方式。盡管OSKit的"部件"在缺省情況下也是被打包成為普通的鏈接庫,但它們的結構則是被設計程面向對象的,而不是傳統的面向函數的方式。與OSKit的函數庫相比,部件庫通常只對外開放一些相關的公用調用接口,而不是大量的功能。例如,在Linux和BSD的驅動程序部件庫裡,每一個完整的驅動程序僅呈現為一個單獨的函數調用,這個調用用來初始化並注冊驅動程序。 1.4.4 執行環境 OSKit中的許多部件在核心和用戶方式下都可以使用,這就需要對部件的執行環境作出定義,例如部件什麼時候可以嵌套進入等。此外,OSKIT使用了許多其它操作系統的代碼,例如設備驅動程序和網絡協議棧,都是原封不動的從原有的核心如BSD和Linux中借用來的,OSKit通過附加代碼模擬原始執行環境使的這些執行模塊比它們原始執行環境更簡單,用戶也不需要詳細了解原執行環境的細節。下面對OSKIT的每種執行模塊進行簡單的介紹。 純執行模塊:這是OSKit執行環境中最簡單的一個模塊,這些部件或函數中沒有全局變量或靜態變量,只使用和處理由目標環境傳遞來的數據。例如函數strlen,它只通過目標環境傳遞給它的字符串指針求出字符串的長度,並將其返回,它只接觸參數數據域,而不影響目標環境。當這些函數使用的數據集是分離的,它們可以安全地同時被調用,而不需要同步,反之則不行。例如對重疊的目標緩沖區並發使用memcpy調用時不安全的。 非純執行模塊:這些模塊中使用了全局變量或有可能改變全局共享狀態,例如liboskit_kern(核心支持庫)中的許多函數建立和訪問全局處理器寄存器和數據結構,因此它們是非純執行模塊。非純執行模塊有以下特點:非純函數和部件可能依賴於全局狀態,如全局變量,靜態變量,處理器中的特殊寄存器等。除非有明確的聲明,非純函數和部件是不可重入的,並且運用在多線程系統中也是不安全的。為了在一個多線程/多處理器環境中使用這些函數和部件,目標操作系統必須提供適當的同步代碼。 阻塞模塊:它擴展了非純模塊來支持非搶先的多線程,這些模塊中有一類可重入的函數稱為阻塞函數,在這模塊中,除非明確聲明為非阻塞函數,否則函數是阻塞的。為了在一個完全搶先的、可中斷的或者多處理器的環境中使用阻塞模塊,必須在進入模塊前加鎖,在退出模塊時將鎖釋放。 可中斷阻塞模塊:在它之中每個部件都是一個單線程的執行域,在一個給定的時刻,只有一個(虛擬的或者物理的)CPU可以執行部件中的代碼。例如:在一個多處理器系統中,在進程級別下,任意時刻在一個部件集內只有一個CPU被允許執行;這能夠通過在部件前後放置全局鎖來實現。 在一段時間內,部件中可以存在多個活動進程,但在某時刻只有一個進程被執行。目標操作系統給每個活動進程提供一個獨立堆棧,這個堆棧在阻塞函數運行時被保留。只有在操作完成後,對部件的調用返回時才放棄該堆棧。部件中的代碼總是運行在兩個級別中之一,進程級或中斷級。有意思的是一些部件的函數和方法只能在進程級別被調用,而另一些只能在中斷級別被調用,還有的能在任何級別被調用。調用的細節屬於接口描述的一部分。 部件中無論進程級別或中斷級別的操作都能被部件中的中斷處理程序中斷,除非代碼調用osenv_intr_disable屏蔽了中斷。當部件在進程級運行時,OSKIT假定中斷開放,部件在處理過程中可能臨時屏蔽掉中斷,但必須在返回到目標操作系統前重新激活。同樣,當部件在中斷級運行時,OSKIT假定中斷被屏蔽,但是部件可以在目標操作系統允許其它中斷級別的活動中斷該部件時重新激活中斷。 當目標操作系統在一個部件內中斷一個進程級別的活動時,在繼續這個活動前,操作系統必須執行完這個中斷級別的活動。同理,若一個中斷級的活動被中斷,那麼最近的中斷級別的活動必須在繼續前一個中斷級別的活動之前完成。部件中運行在中斷級別的代碼不能調用目標操作系統提供的阻塞回調函數;只有非阻塞的回調函數能夠在中斷級別被調用。