第一章 OSKIT介紹
1.1 OSKIT概述
OSKIT 是美國猶它大學計算機科學系FLUX研究組編寫的一套用於架構操作系統內核、服務器和其他OS級軟件的框架及模塊化的部件和庫程序。OSKIT的編寫者認為,操作系統中有很大一部分模塊是系統必須的,但並不是開發者所感興趣的,例如系統裝入模塊,各種標准驅動模塊等。使用OSKIT的目的就是使操作系統的開發者集中精力開發他們操作系統中有特色的,或者他們感興趣的部分,而不必考慮一些繁瑣而乏味的細節。為了達到這個目的,OSKit在設計時借用了COM的思想,把操作系統的各個部分設計成盡量獨立的COM模塊,以方便操作系統的開發者使用或替換。因此,當開發人員使用這套工具時,可以把它當作一個完整的操作系統來使用,也可以根據需要使用其中的一部分,它還可以作為一套動態鏈接庫,由操作系統及支持程序對它進行調用。
1.2 OSKIT的組成
OSKIT把給開發者提供的功能分為三個部分:接口、函數庫和部件庫。
OSKIT的接口非常清晰,而且是面向對象的,它采用了部件對象模型(COM)的架構 ,在文章的後續部分會對OSKIT中的COM接口的組織和結構進行詳細的討論。
OSKIT的函數庫使用傳統的C語言,並采用面向函數的方式向用戶提供服務。例如基本的C庫函數,這些函數易於使用和控制,目標操作系統能很方便的使用其中的某個函數,而且各個函數之間的依賴關系已被縮減至最小。這些函數庫中很少使用OSKit自己的COM接口,取而代之的是在普通的C頭文件中定義面向函數的接口。這種設計策略為操作系統設計人員提供了最大程度的支持和靈活性,以適合任何特殊的操作系統環境。
下面是OSKIT目前提供的主要函數庫列表,以及它們的簡介:
liboskit_c:一個簡單的最小限度的C庫,它在一個受限制的OS環境中提供了通用的C庫支持,並使對環境和其他模塊的依賴達到最小限度。在需要一個更完整的、類POSIX的C函數庫的情況下,可以使用FreeBSD的C函數庫,甚至是用自己編寫的函數庫來代替這個最小化的C庫。
liboskit_kern:建立一個基本的操作系統內核運行環境所需的內核支持代碼,該函數庫為陷入和中斷等提供缺省的處理程序。這個庫包括很多在寫核心代碼時非常有用的通用工具,如訪問某個CPU寄存器,建立並維護頁表,在不同的處理器模式間轉換等。
liboskit_smp:內核支持代碼,這個庫可建立一個便於操作系統使用的多處理器系統環境。
liboskit_com:處理COM接口的工具函數和一套通用的包裝部件。
liboskit_dev:這個庫提供了驅動程序和由其它操作系統引入的部件(如網絡、文件系統)在OSKit中使用時所需要的轉換函數的缺省實現,這些函數工作在OSKit驅動程序框架之中。這些函數的缺省實現是為使用了liboskit_kern庫的簡單內核所設計的,開發者在必要時可以重寫部分或全部函數。
OSKIT的部件庫提供了一個一般情況下更高層的功能,這種功能以一種更加標准、精心修整的、面向對象的"黑盒"方式設計。盡管OSKit的"部件"缺省情況下也是被打包成為普通的鏈接庫,但它們的結構則是被設計成面向對象的,而不是傳統的面向函數的方式。與OSKit的函數庫相比,部件庫通常只對外開放一些相關的公用調用接口,而不是大量的功能。例如,在Linux和BSD驅動程序部件庫,每一個完整的驅動程序僅實現成為一個單獨的函數調用,這個調用用來初始化並注冊驅動程序。目標系統通過OSKit的面向對象的COM接口來與這些部件進行交互,這允許很多部件及部件的實例共存,並以操作系統開發人員定義的方式交互。這種設計的策略有利於將已有系統(如BSD和Linux)中的代碼並入OSKit,那時,隱藏原有環境的細節要比提供靈活性更重要。
下面是目前OSKit所提供的主要部件庫的概要。
liboskit_posix:增加對用POSIX構造的系統通常會實現的系統調用的支持。例如:open、read和write等。這些POSIX操作被映射到相應的OSKit COM接口上。最小化的C庫和FreeBSD C庫都依賴POSIX庫來提供所需要的系統級操作。
liboskit_freebsd_c:源於FreeBSD的完整的類POSIX C庫,提供了對單線程和多線程的支持。在需要支持類POSIX函數或線程安全時,這個庫可以用來替換最小化的C庫。
liboskit_freebsd_m:完整的標准數學庫。可以提供浮點數支持。
liboskit_fsnamespace:文件系統名字空間庫,為應用程序提供了命名接口,可以將多種部件的絕對和相對路徑名轉換成為oskit_file和oskit_dir的COM對象。
liboskit_rtld:運行時鏈接、裝入庫,允許ELF格式的OSKit內核裝入共享庫。
liboskit_lmm:一個靈活的存儲管理庫,它可以管理物理內存或虛擬內存。
liboskit_amm:地址映射管理庫,用於管理系統資源。
liboskit_svm:簡單虛存庫,它使用AMM 庫定義了一個簡單的用於單獨地址空間的虛存接口,提供內存保護和為塊設備如一個硬盤分區進行分頁。
liboskit_threads:這個庫提供了多線程的內核支持,包括POSIX線程、同步、調度和棧保護。
liboskit_diskpart:一個能夠識別多種常見的磁盤分區方案並生成一個完整映射的通用庫。這個庫提供了一個簡單的方法讓操作系統可以找到它所"感興趣的"磁盤分區。
liboskit_fsread:一個簡單只讀文件系統解釋庫,它可用於不同的文件系統,包括BSD FFS、Linux ext2fs和MINIX的文件系統。它通常與liboskit_diskpart一起使用,為操作系統提供從硬盤或軟盤上讀取程序和數據的功能。這個庫也可用於構造裝入程序。
liboskit_exec:一個通用的可執行程序解釋器和裝入器,它支持流行的可執行程序格式,如a.out和ELF。
liboskit_linux_fs:對Linux內核2.0.29版的文件系統部分代碼進行包裝。
liboskit_netbsd_fs:對NetBSD 1.2版的文件系統部分代碼進行包裝。
liboskit_memfs:一個簡單的基於內存的文件系統,對外提供了標准的OSKit文件系統接口。
liboskit_freebsd_net:對FreeBSD 2.1.7.1版網絡代碼的包裝。
liboskit_linux_dev:對Linux內核2.0.29版的設備驅動程序進行包裝。將其包裝在OSKit的設備驅動架構中。
liboskit_freebsd_dev:對FreeBSD 2.1.7.1版的設備驅動程序進行包裝。將其包裝在OSKit的設備驅動架構中。
1.3 OSKIT的設計原則
盡管當前OSKIT的目標平台是X86體系,但將來它會被移植到其它的體系,如StrongArm。
OSKIT的接口被盡可能地設計成便於移植。盡管很大一部分的OSKit代碼是與平台相關的,但它很多的功能接口都是通用的,並且可以在很多其它的體系結構或平台上使用。例如,OSKit的設備驅動程序接口是完全可以移植的。
OSKIT在每一個庫中記錄模塊間的依賴信息。對於函數庫,這意味著要標明不同的函數間的依賴關系;對於部件庫,這意味著要標明每一個庫中不同部件集之間的依賴關系。
1.4 OSKIT的配置
OSKit遵從GNU的配置、編譯和安裝習慣:可以參考源代碼根目錄中的INSTALL文件,在該文件中有使用GNU配置文件的通用方法。簡單的說,需要運行OSKit根目錄中的配置文件,這個文件將會估計出當前系統的類型以及各種所需工具(如C編譯器)的位置。OSKit可以配置成在它自己的目錄中編譯,只要在OSKit源碼的根目錄中用./configure命令就可以了。或者可以將OSKit編譯並安裝到一個單獨的目標目錄中,如果需要這樣,只要在那個新的目錄中運行配置文件就可以了。例如,當源文件存在NFS分區上時,使用一個單獨的目標目錄,可讓你把目標文件放在本地的硬盤上。
此外,OSKit的配置還可以保留多份(當然是在不同的目錄中),每一套配置有自己的目標文件,但共享一份代碼。
當需要為另一種體系結構對OSKit進行交叉編譯時,需要指定宿主機(需要運行OSKit的機器)的類型和編譯機(編譯OSKit的機器)的類型,這可以用configure中的-build=machine和-host=machine選項來實現。由於OSKit是一個單獨的軟件包,並且不使用任何它自己以外的包含文件和庫文件,因此宿主機的操作系統部件與OSKit的配置並沒有直接的關聯。然而,在配置腳本運行時會需要以對宿主機的指定作為一個前綴來尋找各種交叉編譯工具。例如,如果指定"-host=i486-linux",配置腳本會去尋找名為i486-inux-gcc、i486-linux-ar和i486-linux-ld等的編譯工具。在其它工具中,具體使用哪一個工具,由目標文件的格式來決定(如ix86-linux-*工具可以建立ELF格式,而ix86-mach-*工具建立a.out格式)。
OSKit的配置腳本支持很多基本的選項,要得到一份完整的選項列表,可以運行./configure -help。除了這些基本的命令之外,配置腳本還支持下面的這些OSKit特有的選項:
-enable-debug:在編譯時使用這個選項,將會把調試信息包含進來,這會影響一些運行的性能,但當錯誤有可能發生時,會提高檢測錯誤的速度。
-enable-profiling:生成剖析版的OSKit庫,為了與習慣的標准保持一致,剖析版的庫將會有-p作為後綴。
-disable-asserts:不使用assert()調用
-enable-unixexamples:生成在Unix的用戶態下運行OSKit的一些部件的支持代碼和例子程序,目前支持FreeBSD和Linux。
-enable-doc:在編譯時生成OSKit的文檔。這樣做除了要花費很多時間以外,還需要LaTex和dvips。已經生成的.ps和.html格式的文檔在源碼目錄中。
在開始實際編譯OSKit之前,可以做以下的一些事情忽略那些不需要的部分,使編譯工作完成的更快一些:
修改<oskit/dev/linux_ethernet.h>,使其只包括需要的行。除了可以讓編譯速度更快以外,對於某些不兼容的硬件,也需要這樣做,以防止死機。
修改<oskit/dev/linux_scsi.h>,使其只包括需要的驅動程序。
修改<oskit/fs/linux_filesystem.h>,使其只包含需要的文件系統。
修改源碼根目錄下的GNUmakefile,設定SUBDIRS,使其只有需要的目錄。只有在對OSKit有了一些使用經驗之後並且知道你需要哪些目錄以後,才可以這樣做。直接在GNUmakefile.in中指定subdirs也可以達到這樣的目的,那樣做的好處是防止以後再次進行配置時覆蓋曾經做過的修改。
設定環境變量CFLAGS為-O1 -pipe。這會讓編譯器在編譯時所做的優化工作比較少,並且在編譯器不把臨時文件寫到內存虛擬的文件系統時提高編譯的速度。
1.5 OSKIT的編譯
目前,編譯OSKit需要以下的工具:
GNU make
GNU CC (gcc) 2.95.2版。較新版本的gcc和egcs應當也可以使用,但沒有測試過。
GNU binutils 2.8.x或2.9.1 with BFD 2.9.1。
要編譯OSKit,可以到源碼的根目錄(或者是用戶指定的獨立的目標目錄)中運行GNU make(在Linux系統上是make,在BSD系統上是gmake)。請注意OSKit需要GNU make,不能用其它的make工具來代替它。為了防止混淆,OSKit的makefile都命名為GNUmakefile,而不是Makefile,這樣的話,當用戶錯誤地運行了其它的make工具時,它將報告找不到makefile的錯誤,而不是一些其它的錯誤。
要想只編譯或重新編譯OSKit的一部分代碼(例如某個庫),只需進入放置該部分的目標目錄,然後在該目錄中運行GNU make。根目錄中的GNUmakefile實際上不做任何事情,只是在其它各個目錄中遞歸調用make命令。一些OSKit目錄的編譯需要其它的部分首先被編譯,例如核心的例子在它們需要的OSKit庫被編譯之前是無法編譯的。但OSKit的大部分庫都可以在完全不需要其它庫的情況下被編譯。
當OSKit編譯好以後,用戶可以用"make install"命令來安裝它。缺省的情況下,這些庫會被安裝到/usr/local/lib中,而頭文件會被安裝到/usr/local/include中,除非用戶在配置時使用了-prefix選項來指定目標目錄。所有的OSKit頭文件都安裝在oskit/子目錄中(例如/usr/local/include/oskit),這使它們不會和已經存在的任何頭文件沖突。即使是OSKit庫被安裝在主庫的目錄中(如/usr/local/lib),所有的OSKit庫文件都有前綴oskit_,這可避免與其它不相關的庫相混淆。例如,OSKit的最小化C庫命名為liboskit_c.a,而不是libc.a,這使用戶可以在同樣的目錄中安裝一個實際的C庫。
標准的make 變量如CFLAGS和LDFLAGS被OSKit的Build工具所使用,但是在OSKit的makefile中沒有定義,它們可以在命令行上被使用。例如,你可以用命令"make CFLAGS="-save-temps""來使GCC把它編譯過程中產生的中間文件留在目標目錄中(我們內部規定,OSKit的makefile中的變量都以OSKIT_作為前綴。)
1.6 OSKIT的使用
要使用OSKit,可以在開發核心時使用OSKit提供的部件庫、函數庫,並在編譯時將所使用的庫鏈接到核心映象中。在OSKit的examples目錄下有許多的例子核心,開發者可以通過查看以及運行這些核心來學習OSKit中各種庫和映象Makefiels的編寫方法。
OSKit中的庫都進行了很好的設計,用戶可以根據需要替換其中的任何一個庫,由於在OSKit的文檔中對每個庫與其它庫的依賴關系進行了詳細的說明,因此用戶可以很輕松的替換掉某個部件,而不會帶來麻煩。實際上,在很多情況下,特別是在某些函數庫中,為了有效的使用OSKit,是必須要替換掉其中的一些函數或者符號的。要重載某個庫中的函數或者符號,只要在核心或應用程序中再定義一遍就可以了,鏈接程序會確保去使用所定義的函數和符號。OSKIT設計者強烈建議使用鏈接的方法去替換掉OSKit的某個部件,而不是直接修改OSKit的源碼(除了修改源碼中的Bug)。保持自己的核心和OSKit分開,會使升級到新版本的OSKit更加容易。
當完成核心的編寫工作後,使用gcc中的-c選項把核心源碼編譯成一個或多個.o文件,然後創建Gnumakerules文件,把寫好的.o文件與核心中使用的其它庫文件按gcc的規范寫入這個文件中。運行make,這些文件會被鏈接成一個核心。
啟動核心有幾種方法:當OSKit配置成在Linux或其它的使用ELF可執行文件結構的主機上使用時,就可以使用OSKIT提供的mklinuximage 將核心制作成一個標准的可以從LILO或其它Linux引導程序引導的內核映象。當OSKit配置成在Mach或BSD系統上工作時,就可以使用OSKIT提供的mkbsdimage建立一個a.out格式的映象,這個映象可以從任何BSD或Mach的引導程序引導。需要注意的是mkbsdimage 需要GNU ld能夠正常的工作,在BSD系統上,通常都是不使用GNU的ld的,用戶必須要自己手動編譯並安裝GNU的ld。當目標機以DOS 為基礎(如i386-msdos或i386-moss)時,就要使用mkdosimage。和mkbsdimage一樣,mkdosimage也需要GNU的ld,必須要首先安裝GNU的ld,並且將其配置成為可以交叉編譯MS-DOS的文件,然後mkdosimage才能正常的工作。此外,OSKIT還提供了mkmbimage腳本,與其它的腳本不同,它不會做任何的轉換,它只是允許將內核與其它的文件合成一個MultiBoot(有關Multiboot的問題,請見第五章)映象,這個映象可以和MultiBoot引導程序(如GRUB和NetBoot)一起使用。如果在你的系統上安裝了Perl,就可以用一個同樣功能的程序mkmb2來代替它,這個程序工作的更快一些。它的用處和mkmbimage是一樣的。
當用OSKIT制作的核心啟動時可以附加命令行參數,不同的引導程序可以將它們轉化成自己的命令行格式,OSKIT對命令行參數規定了格式:
progname [<boot-opts and env-vars> -]<args to main>
注意,如果沒有"-"的話,所有的參數都將被傳遞給main函數。
缺省的OSKit Multiboot啟動代碼會把這些字符串轉換成為C風格的argv/argc參數,環境變量數組,和記錄在全局變量oskit_bootargv/osit_bootargc中的啟動參數。
argv/argc和環境變量數組會傳遞給main程序,後者通常作為一個名為envp的第三參數。oskit_bootargv/oskit_bootargc中的引導參數會被缺省的OSKit控制台啟動代碼解釋,並且下面的標志有著特殊的意義:
-h:使用串口作為控制台,參考-f標志。具體使用的串口由libkern中的base_console.c中的變量cons_com_port來控制。
-d:激活通過串口使用GDB,具體使用的串口由libkern中的base_console.c中的變量cons_com_port來控制。這個串口可以不同於控制台的串口,實際上最好是這樣。
-p:激活剖析功能。操作系統的內核必須是編譯成為支持剖析功能的。
-k:打開killswitch支持,這允許向第二個串口上傳送字符以中止內核的運行。
-f:當使用一個串行的控制台時,使其工作在115200的速率而不是缺省的9600。這是Utah的擴展而並不在BSD之中。
這些參數都是以BSD 為中心指定的,這是因為在猶它大學OSKIT的設計者通常都是用FreeBSD的引導程序引導內核。
此外,如果使用了NetBoot引導程序,在oskit_bootargv中還會有另一個參數:
-retaddr address
這個參數指定一個OSKit可以跳到並且將控制權還給NetBoot的物理內存地址。Libkern中的bas_console.c中的缺省的_exit函數在退出時使用這個值。
1.7 OSKit的執行環境
OSkit中的許多部件在核心和用戶方式下都可以使用,這就需要對部件的執行環境作出定義,例如部件什麼時候可以嵌套進入等。此外,OSKIT使用了許多其它操作系統的代碼,例如設備驅動程序和網絡協議棧,都是原封不動的從原有的核心如BSD和Linux中借用來的,OSKIT通過附加代碼模擬原始執行環境使得這些執行模塊比它們原始執行環境更簡單,用戶也不需要詳細了解原執行環境的細節。下面對OSKIT的每種執行模塊進行簡單的介紹。
純執行模塊。這是OSKIT執行環境中最簡單的模塊。這些部件或函數中沒有全局變量或靜態變量,只使用和處理目標環境傳遞的數據。例如函數strlen,它只通過目標環境傳遞給它的字符串指針求出字符串的長度,並將其返回,它只接觸參數數據域而不影響目標環境。當這些函數使用的數據集是分離的,它們可以安全地同時被調用,而不需要同步,反之則不行。例如對重疊的目標緩沖區並發使用memcpy調用是不安全的。
非純執行模塊。這些模塊中使用了全局變量或有可能改變全局共享狀態,例如liboskit_kern(核心支持庫)中的許多函數建立和訪問全局處理器寄存器和數據結構,因此它們是非純執行模塊。非純執行模塊有以下特點:
* 非純函數和部件可能依賴於全局狀態,如全局變量、靜態變量、處理器中特殊寄存器等。
* 除非有明確的聲明,非純函數和部件是不可重入的,並且運用在多線程系統中也是不安全的。為了在一個多線程/多處理器環境中使用這些函數和部件,目標操作系統必須提供適當的同步代碼。
阻塞模塊。它擴展了非純模塊以支持非搶占的多線程,這些模塊中有一類可重入的函數稱為阻塞函數,在這模塊中,除非明確聲明為非阻塞函數,否則函數是阻塞的。為了在一個可搶占的、可中斷的或者多處理器的環境中使用阻塞模塊,必須在進入模塊前加鎖,在退出模塊時將鎖釋放。
可中斷阻塞模塊。在它之中每個部件都是一個單線程的執行域,在一個給定的時刻,只有一個(虛擬的或者物理的)CPU可以執行部件中的代碼。例如:在一個多處理器系統中,在進程級,任意時刻在一個部件集內只有一個CPU被允許執行;這能夠通過在部件前後放置全局鎖來實現。
此外,OSKit的執行環境還有以下特點:
在一段時間內,部件中可以存在多個活動進程,但在某時刻只有一個進程被執行。
目標操作系統給每個活動進程提供一個獨立堆棧,這個堆棧在阻塞函數運行時被保留。只有在操作完成後,對部件的調用返回時才放棄該堆棧。
部件中的代碼總是運行在兩個級別中之一,進程級或中斷級。有意思的是一些部件的函數和方法只能在進程級被調用,而另一些只能在中斷級被調用,還有的能在任何級別被調用。調用的細節屬於接口描述的一部分。
部件中無論進程級或中斷級的操作都能被部件中的中斷處理程序中斷,除非代碼調用osenv_intr_disable屏蔽了中斷。
當部件在進程級運行時,OSKIT假定中斷開放,部件在處理過程中可能臨時屏蔽掉中斷,但必須在返回到目標操作系統前重新激活。同樣,當部件在中斷級運行時,OSKIT假定中斷被屏蔽,但是部件可以在目標操作系統允許其它中斷級別的活動中斷該部件時重新激活中斷。
當目標操作系統在一個部件內中斷一個進程級的活動時,在繼續這個活動前,操作系統必須執行完這個中斷級別的活動。同理,若一個中斷級的活動被中斷,那麼最近的中斷級別的活動必須在繼續前一個中斷級別的活動之前完成。
部件中運行在中斷級別的代碼不能調用目標操作系統提供的阻塞回調函數;只有非阻塞的回調函數能夠在中斷級別被調用。