Linux嵌入式應用介紹﹕建立一個嵌入式Linux環境

Linux嵌入式應用介紹﹕建立一個嵌入式Linux環境

Linux嵌入式應用介紹﹕
建立一個嵌入式Linux環境
HungLin Chou.
一, 前言
嵌入式Linux應用技術,這一陣子以來一直是產業界相當熱門的話題之一,我們都知道現在的Linux核心已經可以在多種不同的硬體平台上面運作,而且憑藉著來自各地的好手,我們逐步的讓Linux的技術可以有機會應用在不同的環境中。舉凡在即時系統的應用、以及PDA或是目前流行的機頂盒﹝Set-Top Box﹞,每個不同的應用所牽涉到的硬體與處理器種類都是在解決系統問題時,相當關鍵的一環。
因為硬體的選擇,攸關到將來產品量產時的成本以及在市場上的競爭力,所以說對於嵌入式系統的開發工作者來說,選擇一個具備系統彈性以及在將來擁有廣泛硬體支援的作業系統,將是一個不可或缺的要素。
Linux由於它先天開放與自由的特性,在世界各地眾多好手的加入之下,選擇它做為我們開發的平台,我們可以順利的把環境移植到目前許多當紅和具備競爭力的硬體與處理器上。不可諱言的,Linux有一項最大的優勢就是它具備了豐富的網路協定與驅動程式資源,舉凡Ipv6Mobile IP以及當紅的藍芽﹝Bluetooth﹞,都可以在Linux上找到它們的蹤跡。因為這樣的特點,採用Linux做為嵌入式應用的廠商,可以透過整合現存的程式資源來完成目的,並且可以把主要的心思花費在如何讓產品更具備競爭力與提供更佳的服務。
目前從事Linux研發的公司相當多,不論是檯面上打著Embedded Linux旗號的公司或是在其它領域裡把Linux包含在產品當中,其實或多或少都會用到嵌入式Linux的技巧。雖然,現在有不少這方面的嵌入式Linux快速工具,可是這些工具能做到的功能其實還是有限,許多時候我們需要的是針對特定的硬體與功能來把系統完整的整合。要解決這樣的問題,就不是一般的嵌入式Linux快速開發工具所可以勝任的工作了,這是需要從事開發的人員對於嵌入式的環境有所了解,進而可以真正的利用系統化的角度來整合產品的要求。
許多的Linux公司其實角色有點類似Linux Design House,為何這樣說呢因為這類對於Linux具有專業能力的廠商,可以替周邊有意進入這領域卻沒有Linux研發能力的公司提供專業的技術支援,例如協助它們開發嵌入式Linux的產品或是支援硬體廠商開發Linux上的驅動程式。我想這應該是一個不錯的商業模式,因為Linux產業的興起,吸引許多廠商也樂於投入這樣的領域中,並且這樣會吸引更多有能力的人加入Linux的研發,從而帶動整個產業對於Linux應用的了解與發揮它的多樣性。
二, 介紹LinuxHAL
在正式進入我們的主題前,我打算先跟各位介紹Linux之所以可以在許多平台上移植的要素 ” Hardware AbstractionLayer r” ,因為有了硬體虛擬層的概念,所以作業系統可以把與硬體平台相依的部分切割出來,讓作業系統在日後更換硬體平台時,可以耗用最少的資源成本。
Linux之所以具備了移植到不同硬體平台的能力,就是在於它提供了一個硬體抽象層的概念﹝Hardware AbstractionLayer﹞,透過這樣的架構我們可以把與硬體有關的部分和與硬體無關的部分做一個切割,如下圖
當我們取得Linux核心的原始碼後,我們會發現有一個arch目錄,這個目錄所包含的內容就是Linux與硬體平台有關的程式碼,目前這目錄的內容如下
[root@hlchou arch]# pwd
/hal/linux/arch
[root@hlchou arch]# ls
alpha/ cris/ ia64/ mips/ parisc/ s390/ sh/ sparc64/
arm/ i386/ m68k/ mips64/ ppc/ s390x/ sparc/
[root@hlchou arch]#
每個目錄的名稱則是依據所屬的處理器類型,例如﹕屬於x86系列處理器的程式碼就至於 i386目錄中。
編譯過Linux核心程式碼的使用者應該都知道,Linux編譯到了最後的階段會把各核心所編譯完成的模組連結成一個vmlinux。因此,如下表所示,這是在Strong ARM1110X86平台上在連結成vmlinux時,所會一併連結而成的目的檔。
處理器
檔案名稱
i386
arch/i386/kernel/head.o
arch/i386/kernel/init_task.o
arch/i386/kernel/kernel.o
arch/i386/mm/mm.o
arch/i386/lib/lib.a
lib/lib.a
init/main.o
init/version.o
kernel/kernel.o
mm/mm.o
fs/fs.o
ipc/ipc.o
drivers/block/block.o
drivers/char/char.o
drivers/misc/misc.o
drivers/net/net.o
drivers/media/media.o
drivers/char/agp/agp.o
drivers/char/drm/drm.o
drivers/ide/idedriver.o
drivers/cdrom/driver.o
drivers/pci/driver.o
drivers/pcmcia/pcmcia.o
drivers/net/pcmcia/pcmcia_net.o
drivers/pnp/pnp.o
drivers/video/video.o
net/network.o

ARM

arch/arm/kernel/head-armv.o
arch/arm/kernel/init_task.o
arch/arm/kernel/kernel.o
arch/arm/mm/mm.o
arch/arm/nwfpe/math-emu.o
arch/arm/lib/lib.a
lib/lib.a
init/main.o
init/version.o
kernel/kernel.o
mm/mm.o
fs/fs.o
ipc/ipc.o
drivers/block/block.o
drivers/char/char.o
drivers/misc/misc.o
drivers/net/net.o
drivers/media/media.o
drivers/ide/idedriver.o
drivers/sound/sounddrivers.o
drivers/pcmcia/pcmcia.o
drivers/net/pcmcia/pcmcia_net.o
drivers/video/video.o
drivers/usb/usbdrv.o
net/network.o
之所以把i386Strong ARM放在表中做比較,是因為透過這樣的方式,我們可以發現在不同平台上面Linux核心在連結時所會引入的目的檔。而在arch目錄中我們可以發現i386Strong ARM都會引入相同功能的目的檔,所以如果我們想要查看與硬體有關的部分提供了哪些函式,就可以透過與這些目的檔有關的程式碼來得知。
由表中,我們可以看到i386vmlinux分別連結了arch/i386/kernel/head.oarch/i386/kernel/init_task.oarch/i386/kernel/kernel.oarch/i386/mm/mm.oarch/i386/lib/lib.a,而Strong ARM 1110vmlinux連結了arch/arm/kernel/head-armv.oarch/arm/kernel/init_task.oarch/arm/kernel/kernel.oarch/arm/mm/mm.oarch/arm/nwfpe/math-emu.oarch/arm/lib/lib.a。這些在arch目錄中的程式碼,都是與硬體有關聯的程式碼,也就是說在Linux的環境中必須要把arch目錄下的函式與和硬體有關的功能實作完成,透過這些與硬體有關的基本函式,在上層運作的其它模組,便可以在最小修改的情況下,適用於這樣的架構。如下圖所示


因為有了hal層與Linux上其它核心模組的運作,所以在Linux環境下運作的使用者程式,可以在不需要修改的情況下,便可以透過跨平台的編譯器來重新編譯,以使得它可以在不同的平台順利運作。
其實,同樣的概念在Windows 2000上面也一樣存在,例如﹕Windows2000有實作一個hal.dll/winnt/systen32/hal.dll﹞,這個檔案就是提供一個與下層硬體無關的介面給上層的驅動程式與Windows 2000核心呼叫,讓上層呼叫hal層的核心,可以無須考慮硬體的因素,與硬體平台有關的部分可透過hal.dll來負責處理。
如下圖,就是Windows 2000hal層提供給核心呼叫的函式示意圖


其實,我們不難發現這樣的概念對於作業系統的實作來說,著時帶來不少好處,因為上下層的關係有所區隔所以可以讓移植作業系統的人員,可以把重心放在與硬體有關的層次上,致力於把硬體相關的函式實作完整,如此便可以確保上層的系統呼叫也一併可以順利運作。
三, 函式庫環境
在嵌入式的應用上,執行環境的空間大小除了攸關系統資源的耗用外,還關係到產品在商品化時,成本的考量。而Linux使用者環境所包含的動態函式庫檔案,即是一個佔用不少執行環境空間的元件。
其實不論是否有真正的對函式庫做過縮小化的動作,我們必須體認到的一點就是,移除了動態函式庫中部分的功能或是模組,雖然可以減少執行空間的耗用,不過這也代表了該嵌入式執行環境下,將會缺少了可以執行該項函式或是模組的能力。
舉個例子來說,如果我們把Glibc套件當中的libc.so.6裡部分的函式與模組刪除,我們或許可以得到一個500Kbytes大小的動態函式庫,不過相對的這就代表了我們將無法提供一個基本的libc環境給其它的程式,例如﹕目前的PDA或是Set-Top Box將無法任意讓使用者傳入一個使用libc的執行檔,因為這樣精簡型的環境中,主要提供的函式與模組都是針對這環境所預設會執行的執行檔所需要的函式,外來的執行檔如果使用了現在不包含在libc當中的函式時,會在程式啟動時發生錯誤。
如果讀者對於Linux動態函式庫環境有興趣的話,還可以參考筆者的其它作品 http://www.linuxfab.cx/indexColumnData.php?CID=97&FIRSTHIT=1
四, 打造嵌入式的Linux環境
這篇文章主要的目的之一就是要讓各位可以親手打造一個嵌入式的Linux環境,說真的這一點也不難,相信各位在看過我舉的例子之後,應該也會有一樣的感覺。不過說在前頭的就是,這是最基本的概念,當然囉如果使用者希望應用在不同的硬體平台或是特定的周邊裝置上,那就需要具備建立Cross-Compiler與對於周邊裝置的基本概念。以便於可以把Linux嵌入式的概念移植到不同的環境下,不過這可還需要進行一些額外的步驟,需要讀者自行付出努力,親手去產生一個跨平台的編譯器,進而建構一個可以在其它平台運作的Linux環境。如下圖所示


筆者在此提供一個簡易的Script檔用來包裝一個使用者執行環境,讀者可以自行把一個使用者執行環境壓縮成一個可以透過RAMDISK載入執行的使用者執行環境壓縮檔。使用的方式如下﹕
首先可以看到Script MakeImage與目錄base,而檔案linux為筆者所提供的Linux核心,會在開機時啟動RAMDISK來載入製作完成的使用者執行環境,而syslinux.cfg 則是給Syslinux啟動時使用的設定檔
﹝讀者可以到http://www.ibiblio.org/pub/Linux/system/boot/loaders/下載Syslinux]
[root@hlchou sample]# ls
MakeImage* base/ linux* syslinux.cfg*
[root@hlchou sample]#
其中目錄base就是我們所要建構的使用者執行環境,內容如下
[root@hlchou sample]# cd base
[root@hlchou base]# du
4.0k ./lost+found
92k ./bin
4.0k ./dev
12k ./etc/rc.d
72k ./etc
1.2M ./lib
4.0k ./proc
360k ./sbin
4.0k ./tmp
4.0k ./usr
4.0k ./var
1.8M .
[root@hlchou base]#
讀者如果想要在嵌入式環境中加入檔案的話,只要直接拷貝到base目錄下的對應目錄即可,並且透過MakeImage來壓縮成使用者執行環境的檔案,如下
[root@hlchou sample]# ls
MakeImage base linux syslinux.cfg
[root@hlchou sample]# ./MakeImage
4096+0 records in
4096+0 records out
8192+0 records in
8192+0 records out
[root@hlchou sample]# ls
Image.gz MakeImage base linux syslinux.cfg
[root@hlchou sample]#
其中的 Image.gz 就是透過MakeImage所產生的壓縮檔,它是透過把base目錄下的檔案壓縮而成。
如下圖所示,這裡面包含了動態函式庫所存在的目錄﹝”/lib”﹞,以及系統設定檔目錄﹝”/etc”….…等。如果使用者希望可以精簡化整個嵌入式環境的大小,其實就可以從這個使用環境的架構下來著手,如果要加入新的伺服器或是指令,讀者只要把伺服器的程式碼針對所要執行的評台進行編譯後,再移入這個環境即可



在把程式移入到使用者執行的環境時,要注意的一點就是可以透過指令ldd來確認使用者程式使用到了哪些動態函式庫,進而把這些動態函式庫載入到我們的環境中,以確保程式在我們所配置的使用者環境中可以正確執行無誤
[root@hlchou sbin]# ldd init
libc.so.6 => /lib/libc.so.6 (0x4001c000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
[root@hlchou sbin]# ldd route
libc.so.6 => /lib/libc.so.6 (0x4001c000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
[root@hlchou sbin]#
其實,筆者滿建議各位可以透過Busybox來建構使用者環境的,因為BusyBox提供了一組基本的Linux指令。傳統上,我們在Linux使用者環境中所執行的指令都是許多個別的執行檔,不過這會產生一個問題就是這些指令所存在的執行檔,其實有許多的功能是我們所不常使用或是有部分的程式碼是可以重複利用的。而BusyBox這個免費的工具程式,就是為了解決這個問題所產生了,它把許多的Linux指令整合在同一個執行檔當中,並且針對這些執行檔提供充分的功能,讓使用者幾乎可以只利用BusyBox便完成一個實用的使用者環境。
如下圖所示,這是BusyBox的示意圖



透過這樣把指令整合的方式,我們可以達成縮減Linux使用者執行環境指令執行檔所需的空間。
當然在根目錄當中會包含了一個 /linuxrc,其中/linuxrcSymbol Link”/sbin/init”,也就是說當Linux核心啟動後第一個會執行的使用者程式就是/linuxrc。透過這個程式,我門可以讓它去讀取系統的設定檔進而對於我們所預設的使用者執行環境進行初始化,例如我們可能需要載入特定的伺服器與應用程式,就可以在這些過程中加入,並且讓系統在初始過程中可以自動的啟動
如下圖所示,這是我們Linux環境啟動的流程



由圖中我們可以看到在Linux的核心原始碼中﹝init/main.c﹞, 就有這樣一段程式碼,會在核心初始化告一段落後,去執行程式 ”/linuxrc”
如果讀者想要自行編譯核心來使用筆者所提供的範例,請記得要在核心設定時把以下的選項加入
RAM disk support (CONFIG_BLK_DEV_RAM) [Y/m/n/?] y
Default RAM disk size (CONFIG_BLK_DEV_RAM_SIZE) [4096]
Initial RAM disk (initrd) support (CONFIG_BLK_DEV_INITRD) [Y/n/?] y
重新編譯過的核心,請把它的檔名改成linux,並且連同Image.gz﹝使用者執行環境的壓縮檔﹞以及syslinux.cfg 拷貝到syslinux所安裝的磁區即可。以便於可以在核心啟動的過程中,啟動RAMDISK來載入我們的測試環境。
讀者可以到以下網址下載測試的範例檔案﹕
http//xxx.xxx.xxx.xxx
五, 結語
感謝這次編輯raymond的邀稿,讓筆者有機會可以針對嵌入式的環境做一個介紹,並且帶領各位親手做出一個嵌入式的環境,透過這樣的介紹希望可以讓更多人對於嵌入式Linux的環境能夠有一個入門的機會,各位一定會發現其實整個過程並不困難,這是因為Linux上面有許多前人已經把與硬體有關的驅動程式還有核心架構給移植完畢,所以我們現在才會有這樣一個容易使用的Linux核心。
筆者目前所服務的單位主要從事於第三代手機無線通訊系統的研發,其中RT-Linux就是我們目前所考慮的平台,我一直都相信Linux的應用將是無遠弗屆的,並不是打著Linux旗號的公司才是真正懂得應用Linux的公司。其實筆者所接觸過的許多人與團隊,雖然並沒有以Linux做為號召,不過其實產品的核心就是透過Linux來組合而成的。
我相信,Linux是產品在商品化的選擇之一,在思考產品的定位與市場時,不見得要從Linux的角度來思考,因為這樣會嚴重的侷限了我們思考的角度與窄化了我們的想像力。我們應該去思考這世界目前還需要的事物與技術,首先有了產品的點子與雛形再進一步的去思考所要達成這個目的要具備的能力以及技術條件。其中,Linux便是我們可以採用的作業系統之一,我們該選擇最適合達成目的的作業系統,例如可能在許多的即時系統應用上VxWork就比Linux好上許多。
希望台灣的軟體工業可以真正的發展起來,不論到時Linux的進展如何。


Solaris 8系統核心介紹

Solaris 8系統核心介紹


零,前言
筆者使用Solaris的經驗並不久,之前花了很多時間在LinuxWindows的平台上撰寫驅動程式,這次因為一個工作機會所以在Solaris上面撰寫驅動程式,從中讓我了解到不同系統的差異性,也因此我希望可以有機會把我的經驗與各位分享,讓日後有需要撰寫Solaris上面驅動程式或是系統設計的朋友們,可以有一個參考的例子。
當然囉,各位熟悉Solaris平台的玩家們對於上面驅動程式的開發環境應該都有所了解,在Solaris上面常用來開發的介面就是DDI/DKI (Device Driver Interface / Driver Kernel Interface),透過這些介面可以使用Solaris所定義的一些驅動程式介面,對於撰寫核心程式碼的人來說可以省去不少事情。而且,Solaris背後有Sun在主推這套系統,也因此有機會獲得許多大型企業或是重視系統穩定的開發者採用。
本文,主要是以Solaris 8為主,這是筆者唯一使用過的Solaris平台,如果各位有何意見或是看法,隨時歡迎與我聯繫..….^_^
一, Solaris DDI and DKI
寫過Linux Device Driver的朋友應該會注意到一件事情,這就是在Linux平台上面一直都沒有一個公定的驅動程式設計介面來讓所有人遵循,舉個例子來說,如果現在我們要在Linux上面開發驅動程式時,大多數人的做法都是去看/proc/ksyms中所列出的Linux核心函式,再參考核心的程式碼來了解該如何使用這些函式。
相對於LinuxSolaris上面有非常完備的驅動程式設計介面,Sun把它命名為DDIDevice Driver Interface﹞與 DKIDriver Kernel Interface﹞,透過這些介面所寫的驅動程式,我們可以確保在Solaris平台上面的相容問題,就算在Sparc或是i86pcx86﹞的平台上,也可以比較容易達到Source Code 層面的平台相容性。也就是說,如果我們透過DDIDKI的介面來撰寫我們的驅動程式時,我們所開發的驅動程式可以儘可能避免顧慮到太多硬體相容問題,而這些需要依各平台而做不同調整的工作,就由Sun來幫我們處理掉了。在Solaris 8的環境中,DDI的函式是實作在 “/kernel/genunix”中,我們可以透過指令nm來得知這樣的結果
 /usr/ccs/bin/nm /kernel/genunix
…………
[3333] | 532448| 12|FUNC |GLOB |0 |1 |ddi_add_child
[3447] | 550456| 40|FUNC |GLOB |0 |1 |ddi_add_eventcall
[2917] | 518812| 12|FUNC |GLOB |0 |1 |ddi_add_fastintr
[2447] | 518800| 12|FUNC |GLOB |0 |1 |ddi_add_intr
[2083] | 518824| 48|FUNC |GLOB |0 |1 |ddi_add_softintr
[1048] | 550024| 160|FUNC |LOCL |0 |1 |ddi_all_drivers_devid_to_
devlist
[3472] | 532480| 224|FUNC |GLOB |0 |1 |ddi_append_dev
[1038] | 536420| 124|FUNC |LOCL |0 |1 |ddi_append_minor_node
[3643] | 516844| 12|FUNC |GLOB |0 |1 |ddi_apply_range
[3622] | 532244| 8|FUNC |GLOB |0 |1 |ddi_binding_name
[4525] | 518484| 44|FUNC |GLOB |0 |1 |ddi_btop
[2406] | 518528| 44|FUNC |GLOB |0 |1 |ddi_btopr
…………
透過nm我們可以發現,實際上DDI的函式要存在系統中,首先就必須要把genunix載入到核心記憶體中,而genunix也正是整個Solaris系統的主要核心,它提供了許多驅動程式模組最基本的核心函式,所以在Solairs 8的架構中,genunix會在 krtldKernel Loader﹞之後,緊接著備載入到系統中,藉此才能建構一個基本的Solaris核心運作環境。
如下圖﹝一﹞,我們可以看到在Solaris 8環境中載入的Kernel模組,其中DDI函式是由genunix在載入Kernel後所提供的。

圖﹝一﹞
二,檔案格式
Solaris所使用的檔案系統格式為ELF(Executable and Linkable Format),與Linux所採用的檔案格式相同,在這個段落中我們會試著去檢視Solaris上面執行檔、目的檔以及驅動程式的檔案格式,讓各位可以先有一個初步的了解。
如下圖﹝二﹞所示,這是Solaris上面一般的執行檔,我們可以注意到它的e_typeET_EXEC,這就是在ELF中屬於執行檔的屬性。相對的如下圖﹝三﹞所示,這是在Solaris上面ELF目的檔的格式,我們可以注意到它的e_type屬性為ET_REL,也就是Relocatable
圖﹝二﹞

圖﹝三﹞
接下來,我們試著在Solaris中編譯我們的驅動程式

 
/usr/local/bin/gcc -D_KERNEL -c driver.c
/usr/ccs/bin/ld -r -o driver driver.o

其中,指令 “ld” 加上一個 “-r” 的參數是代表在連結的過程中,”ld” 將不會對尚未解決的參考符號﹝unresolved references﹞發出錯誤的訊息。
編譯完成後,我們來檢視驅動程式編譯後產生的檔案格式。如下圖﹝四﹞,我們發現Solaris上面驅動程式的檔案格式與目的檔的檔案格式是一致的,Solaris的驅動程式中所包含尚未解決的參考符號﹝unresolved references﹞將會在驅動程式載入核心的過程中才動態的進行處理。也就是說,Solaris驅動程式所呼叫到的核心函式都是在載入系統時才進行動態連結的,這樣的概念與Linux上面的驅動程式概念是一致的。而且,Solaris也支援如同Linux上面的動態載入驅動程式,對於驅動程式的架構來說算是相當有彈性的做法。

圖﹝四﹞
三,Solaris記憶體結構
Solaris目前有Sparc以及X86的平台上面的版本,在筆者寫這篇文章的同時,我主要是在UltraSPARC-IIi 440MHz的機器上面進行我日常的工作。同時,我也把Solaris 8安裝在我的X86機器上,其中我發現了一些有趣的部分,像是在UltraSPARC上的sun4u版本,它的記憶體架構與在x86上的i86pc版本就有許多的不同,不過筆者對於Sparc的架構其實不是很熟悉,所以在撰寫測試程式的過程中,其實有些結果讓我滿意外的,我想主要是因為與過去在X86平台上面看到的結果有所不同。
首先,Solaris Internals這本書上有sun4csun4m(圖﹝五﹞)sun4d(圖﹝六﹞)的記憶體架構圖。雖然我沒有使用過這三套系統,不過由這些參考資料中,可以了解到一個系統設計上的演進,其實滿有趣的。

圖﹝五﹞
圖﹝六﹞
接下來,重點就放在我目前使用的Solaris sun4u版本,這個版本讓我比較意外的是它的Kernel Mode記憶體位置竟是在整個虛擬記憶體的中間,與sun4csun4msun4d甚至是X86的版本都相當的不同,我沒有花時間在探索Sparc的硬體對於支援這樣架構的優劣點,不過,我把實際上測試的結果繪成如下圖﹝七﹞

圖﹝七﹞
如圖﹝七﹞所示,在sun4u的系統中,應用程式的堆疊會由虛擬記憶體的最上層由上往下延伸,接下來便是應用程式所載入的動態函式庫,由0xFF3E0000(可能因應用程式而有所不同)往下延伸。而應用程式的載入起始點為0x00010000,以該位址為起點依序把ELF檔的內容對應到記憶體中。而最大的不同就在於Kernel Mode的記憶體位置是位於 0x10000000 – 0x80000000 之間,不過我想這個數值應該是可以調整的,不過筆者沒有試著去找出這樣的修改方式。
相對於sun4u的架構,筆者也在X86上安裝Solaris 8後,進行一樣的測試,結果如下圖﹝八﹞

圖﹝八﹞
其實,我們很容易可以發現在X86的機器上面,Solaris 8Kernel Mode 是劃分到最上層的記憶體位址,而真正屬於Kernel Mode的記憶體大小為512MB,應用程式使用的動態函式庫載入位址則由0xE000000開始向下延伸。應用程式載入的起始點則由0x08050000開始。
四,System Call
作業系統核心通常都會提供一組系統呼叫,透過這些由核心所提供的系統呼叫,使用者的程式可以充分的利用系統所提供的功能,進一步的來發揮所在系統平台的特性與優勢。以我們常見的Windows NT平台來說,它允許使用者的程式透過2E中斷來使用Windows NT所提供的系統呼叫,在Linux的平台上,則是透過80號中斷,來達成這樣的任務,而在Solaris呢﹖ 這就是筆者在這段落所要介紹的部分,我會舉出一些實際的例子來驗證Solaris是如何讓使用者透過它的系統呼叫來完成工作。
在這一個段落中,筆者將以X86的機器為主要的實驗平台,我安裝的版本為Solaris 86/00﹞, 我沒有用過Solaris 8以外的版本,不過我相信基本上Solaris X86上的實作方式應該不至於差異太大,如果有一些系統差異上的問題,歡迎各位可以隨時給我一些建議。
首先,我先介紹一下在X86的保護模式環境下作業系統可以透過哪些方式提供系統呼叫給位於User Mode的應用程式
﹝1﹞ Interrupt Gates
e由使用者的應用程式,透過呼叫中斷的方式轉換特權等級到Kernel ModeRing 0﹞來使用核心在Kernel Mode中所實作的系統呼叫。
﹝2﹞ Call Gates
e透過一個遠程呼叫﹝Far Call﹞,改變特權等級至Kernel ModeRing 0﹞中,如此讓在User ModeRing 3﹞的程式,可以藉此呼叫並執行位於Kernel ModeRing 0﹞的程式碼。
如下圖,這是在Intel文件上面可以看到的Protection Rings,而Call Gates的存在就是讓User Mode的程式,可以執行Kernel Mode的程式碼,不過相對來說,User Mode的程式碼比起Kernel Mode的程式碼可靠度較低,所以Call Gates只能在相同等級或是由低特權等級到高特權等級的過程才適用,並無法透過Call Gates在較高特權等級的程式中來執行低特權等級程式中的程式碼,例如﹕無法透過Call GatesKernel ModeRing 0﹞中呼叫並執行User ModeRing 3﹞程式碼。

圖﹝九﹞
Linux的環境中,系統提供了以80號中斷為基礎的System Call,也就是說我們所撰寫的程式,可以透過80號中斷來使用Linux Kernel所提供的系統服務,我們舉個例子來說,如下
int my_fork()
{
__asm__(“
movl $0x2,%eax //SYS_fork
int $0x80
“);
}
段程式碼,是在Linux上面,透過80號中斷呼叫系統所提供的fork函式。對於Linux80號中斷所提供的System Call有興趣的讀者,可自行參閱Linux Kernel原始碼中的檔案 “/arch/i386/kernel/entry.S “ 
不過在Solaris 8 for X86的版本上,可就不是透過80號中斷來實作它的系統呼叫了,我們可以參考如下的程式碼﹕
 int my_fork()
{
__asm__(“
movl $0x2,%eax //SYS_fork
lcall $0x27,$0x0
“);
}
Solaris上面實際上是透過一個長程的呼叫,透過Call Gates機制引發特權等級的切換,來使用系統所提供的System Call
在我們繼續討論之前,我們可以先透過檔案“/usr/include/sys/syscall.h” 來得知目前Solaris 8平台上面提供了哪些系統呼叫,我舉一部份的內容如下﹕

很明顯的,我們可以看到Solaris for i86pc的版本中,是透過遠程呼叫的方式來使用System Call。不過,除了核心所提供的系統呼叫可以透過遠程呼叫的方式來完成以外,還有一些核心提供的呼叫,是允許使用者程式透過中斷的觸發來使用的,例如gethrvtime ()” 是透過 0xd2 中斷來呼叫的﹕
….…
#define SYS_exit 1
#define SYS_fork 2
#define SYS_read 3
#define SYS_write 4
#define SYS_open 5
#define SYS_close 6
#define SYS_wait 7
….…
#define SYS_mount 21
#define SYS_umount 22
#define SYS_setuid 23
#define SYS_getuid 24
#define SYS_stime 25
#define SYS_pcsample 26
….…
目前Solaris 8版本中,共定義了 256System CallNumber﹝由0–255﹞,如果各位要直接在Solaris上透過組合語言來寫程式的話,那System Call的呼叫會是一個很不錯的解決方案。如果透過libc所包裝好的函式,那樣寫起程式來會比較有彈性,因為不同平台的組合語言以及中斷的參數會有所不同,如果透過libc所包裝好的函式,那樣在不同硬體平台上的Solaris應用程式,就可以維持一定程度的相容性。
接下來,在我用C寫的程式中加入部分的組合語言,來驗證我所呼叫的System Call與用libc呼叫所得到的結果是否一致,來得知這些System Call的呼叫是否真的有發生作用,程式碼如下﹕

程式的執行結果如下﹕
我們可以看到,像是my_fork()my_gethrtime()my_getppid()my_getuid()都可以順利的運作,像是因為我們呼叫了fork()System Call所以程式碼在my_fork()之後,便會產生兩個Process來執行之後的程式碼,也因此我們可以看到兩份由printf()所秀出來的顯示字串。其它像是getppid()getuid()直接透過System Call版本的函式與透過libc呼叫的函式所傳回的結果均相同,由此我們可以得知我們直接呼叫System Call函式的結果是正確的。
如下圖,這是在Solaris 8當中所提供的兩種機制,讓使用者程式可以呼叫Solaris 核心所提供的系統呼叫。

#include <stdio.h>
#include <sys/syscall.h>
//
int my_gethrtime();
int my_getppid();
int my_getuid();
int my_fork();
int my_sync();
//
int main()
{
my_fork();
my_sync();
printf(“\ngethrtime:%xh_%xh”,my_gethrtime(),gethrtime());
printf(“\nppid:%xh_%xh”,my_getppid(),getppid());
printf(“\nuid:%xh_%xh”,my_getuid(),getuid());
}
int my_gethrtime()
{
__asm__(“
movl $3,%eax //gethrtime
int $0xd2
“);
}
int my_getppid()
{
__asm__(“
movl $0x14,%eax
lcall $0x27,$0x0
movl %edx,%eax
“);
}
int my_getuid()
{
__asm__(“
movl $24,%eax //SYS_getuid
lcall $0x27,$0x0
movl %edx,%eax
“);
}
int my_fork()
{
__asm__(“
movl $0x2,%eax //SYS_fork
lcall $0x27,$0x0
“);
}
int my_sync()
{
__asm__(“
movl $36,%eax //SYS_sync
lcall $0x27,$0x0
“);
}

Solaris上面實際上是透過一個長程的呼叫,透過Call Gates機制引發特權等級的切換,來使用系統所提供的System Call
在我們繼續討論之前,我們可以先透過檔案“/usr/include/sys/syscall.h” 來得知目前Solaris 8平台上面提供了哪些系統呼叫,我舉一部份的內容如下﹕

很明顯的,我們可以看到Solaris for i86pc的版本中,是透過遠程呼叫的方式來使用System Call。不過,除了核心所提供的系統呼叫可以透過遠程呼叫的方式來完成以外,還有一些核心提供的呼叫,是允許使用者程式透過中斷的觸發來使用的,例如gethrvtime ()” 是透過 0xd2 中斷來呼叫的﹕
….…
#define SYS_exit 1
#define SYS_fork 2
#define SYS_read 3
#define SYS_write 4
#define SYS_open 5
#define SYS_close 6
#define SYS_wait 7
….…
#define SYS_mount 21
#define SYS_umount 22
#define SYS_setuid 23
#define SYS_getuid 24
#define SYS_stime 25
#define SYS_pcsample 26
….…
目前Solaris 8版本中,共定義了 256System CallNumber﹝由0–255﹞,如果各位要直接在Solaris上透過組合語言來寫程式的話,那System Call的呼叫會是一個很不錯的解決方案。如果透過libc所包裝好的函式,那樣寫起程式來會比較有彈性,因為不同平台的組合語言以及中斷的參數會有所不同,如果透過libc所包裝好的函式,那樣在不同硬體平台上的Solaris應用程式,就可以維持一定程度的相容性。
接下來,在我用C寫的程式中加入部分的組合語言,來驗證我所呼叫的System Call與用libc呼叫所得到的結果是否一致,來得知這些System Call的呼叫是否真的有發生作用,程式碼如下﹕
#include <stdio.h>
#include <sys/syscall.h>
//
int my_gethrtime();
int my_getppid();
int my_getuid();
int my_fork();
int my_sync();
//
int main()
{
my_fork();
my_sync();
printf(“\ngethrtime:%xh_%xh”,my_gethrtime(),gethrtime());
printf(“\nppid:%xh_%xh”,my_getppid(),getppid());
printf(“\nuid:%xh_%xh”,my_getuid(),getuid());
}
int my_gethrtime()
{
__asm__(“
movl $3,%eax //gethrtime
int $0xd2
“);
}
int my_getppid()
{
__asm__(“
movl $0x14,%eax
lcall $0x27,$0x0
movl %edx,%eax
“);
}
int my_getuid()
{
__asm__(“
movl $24,%eax //SYS_getuid
lcall $0x27,$0x0
movl %edx,%eax
“);
}
int my_fork()
{
__asm__(“
movl $0x2,%eax //SYS_fork
lcall $0x27,$0x0
“);
}
int my_sync()
{
__asm__(“
movl $36,%eax //SYS_sync
lcall $0x27,$0x0
“);
}
程式的執行結果如下﹕
我們可以看到,像是my_fork()my_gethrtime()my_getppid()my_getuid()都可以順利的運作,像是因為我們呼叫了fork()System Call所以程式碼在my_fork()之後,便會產生兩個Process來執行之後的程式碼,也因此我們可以看到兩份由printf()所秀出來的顯示字串。其它像是getppid()getuid()直接透過System Call版本的函式與透過libc呼叫的函式所傳回的結果均相同,由此我們可以得知我們直接呼叫System Call函式的結果是正確的。
如下圖,這是在Solaris 8當中所提供的兩種機制,讓使用者程式可以呼叫Solaris 核心所提供的系統呼叫。
# ./test
gethrtime:7bb0bf36h_7bb0b799h
ppid:1d7bh_1d7bh
uid:3eeh_3eeh
gethrtime:8a646195h_8a645a42h
ppid:1d7ah_1d7ah
uid:3eeh_3eeh
#


圖﹝十﹞
五,驅動程式的載入
Solaris 8的系統中,有一個ksyms (kernel symbols driver)的驅動程式﹝位於/usr/kernel/drv/ksyms﹞,它會透過一個Kernel Thread來動態的更新目前KernelSymbol Table,每當我們載入或是卸下一個驅動程式時,它就會自動的去更新檔案 “/dev/ksyms”,也就是說,如果我們想要知道目前驅動程式有哪些函式可以呼叫時,就可以去檢視這一個檔案,不過這個檔案是以ELF檔的格式存在於Solaris系統中,因此在我們檢視這檔案內容時,我們需要透過一些工具程式讓我們可以比較容易的去閱讀檔案中的內容。我們看到Solaris /dev/ksyms 檔案時,其實熟悉Linux的讀者一定很自然的會想到Linux上的 “/proc/ksyms”,與Solaris不同的地方在於Linux上的ksyms是以單純的文字檔格式存在,透過這些資訊,我們可以很清楚的了解目前系統中所提供的核心函式,這對於我們撰寫該平台上的驅動程式會有相當大的助益。
如下,我們可以透過 elfdump來檢視 /dev/ksyms中的函式資料
# /usr/ccs/bin/elfdump /dev/ksyms
……………….
[1] 0xfe8051c4 0x0000000e FUNC LOCL 0 ABS kadb_error10
[2] 0xfe81dffe 0x00000000 NOTY LOCL 0 ABS audit_anchorpath_L
[3] 0xfe8051d4 0x0000000e FUNC LOCL 0 ABS kadb_error11
[4] 0xfe8051e4 0x0000000e FUNC LOCL 0 ABS kadb_error12
[5] 0xfe8051f4 0x0000000e FUNC LOCL 0 ABS kadb_error13
[6] 0xfe805204 0x0000000e FUNC LOCL 0 ABS kadb_error14
[7] 0xfe805214 0x0000000e FUNC LOCL 0 ABS kadb_error15
[8] 0xfec024bc 0x00000008 OBJT LOCL 0 ABS kstat_hash_table
[9] 0xfe81d022 0x00000000 NOTY LOCL 0 ABS recv_L
[10] 0xfe805224 0x0000000e FUNC LOCL 0 ABS kadb_error16
[11] 0xfec04f10 0x00000000 NOTY LOCL 0 ABS sec_svc_control_inf
……………….
[7048] 0xfea777c4 0x00000250 FUNC LOCL 0 ABS fdc_enhance_probe
[7049] 0xfe80e900 0x00000014 FUNC GLOB 0 ABS ddi_dma_unbindhdl
[7050] 0xfe80e8c4 0x00000014 FUNC GLOB 0 ABS ddi_dma_allochdl
[7051] 0xfe81d3b0 0x0000001a FUNC GLOB 0 ABS lm_shrlock
……………….
其中,標示為 “FUNC” Symbol就是我們可以在撰寫Solaris驅動程式時呼叫的函式。
接下來,筆者實際把驅動程式載入與卸下,來檢視整個系統的運作過程。首先,我們載入驅動程式

add_drv -m”* 0777 other other” -i pci1146,6750 pci554

並且透過工具 modinfo來檢視目前系統中所有已經載入的驅動程式模組
我們可以看到,剛剛我們載入的驅動程式pci554已經列在系統目前已載入的模組資料中了,接下來我們檢視“/dev/ksyms” 來看看驅動程式 pci554中的函式、全域變數….等,是否也會列在該檔案中。
像是在pci554程式中的函式
# modinfo
Id Loadaddr Size Info Rev Module Name
6 10126000 42cb 1 1 specfs (filesystem for specfs)
8 1012bb88 2d38 1 1 TS (time sharing sched class)
9 1012e450 894 – 1 TS_DPTBL (Time sharing dispatch table)
10 1012e4d4 264cf 2 1 ufs (filesystem for ufs)
11 10152f07 10a4c 226 1 rpcmod (RPC syscall)
11 10152f07 10a4c 1 1 rpcmod (rpc interface str mod)
12 10161b97 5520c 0 1 ip (IP Streams module)
……………
107 1025e5a2 279a 88 1 devinfo (DEVINFO Driver 1.31)
108 102ee3b5 1526 70 1 pci554 (PCI21554 driver v0.00)
109 102fc2bb b5b 176 1 inst_sync (instance binding syscall)
110 1012afa7 b2c 23 1 ptm (Master streams driver ‘ptm’)
111 10300c21 c2e 24 1 pts (Slave Stream Pseudo Terminal dr)
……………
並且透過工具 modinfo來檢視目前系統中所有已經載入的驅動程式模組
# modinfo
Id Loadaddr Size Info Rev Module Name
6 10126000 42cb 1 1 specfs (filesystem for specfs)
8 1012bb88 2d38 1 1 TS (time sharing sched class)
9 1012e450 894 – 1 TS_DPTBL (Time sharing dispatch table)
10 1012e4d4 264cf 2 1 ufs (filesystem for ufs)
11 10152f07 10a4c 226 1 rpcmod (RPC syscall)
11 10152f07 10a4c 1 1 rpcmod (rpc interface str mod)
12 10161b97 5520c 0 1 ip (IP Streams module)
……………
107 1025e5a2 279a 88 1 devinfo (DEVINFO Driver 1.31)
108 102ee3b5 1526 70 1 pci554 (PCI21554 driver v0.00)
109 102fc2bb b5b 176 1 inst_sync (instance binding syscall)
110 1012afa7 b2c 23 1 ptm (Master streams driver ‘ptm’)
111 10300c21 c2e 24 1 pts (Slave Stream Pseudo Terminal dr)
……………
我們都可以在該檔案中找到載入Kernel Mode後的相關訊息

 
# /usr/ccs/bin/elfdump /dev/ksyms
……………
[5353] 0x102ef414 0x00000028 FUNC LOCL 0 ABS pb_strategy
[5354] 0x102ee3b8 0x0000006c FUNC LOCL 0 ABS pci21554_interrupt_gen
[5355] 0x102ef0e0 0x000000a4 FUNC LOCL 0 ABS pb_detach
……………
[13727] 0x102ee690 0x00000734 FUNC GLOB 0 ABS Cfg_21554_mem_space
[13728] 0x10479b34 0x00000004 OBJT GLOB 0 ABS bar2addr
[13729] 0x10479b20 0x00000004 OBJT GLOB 0 ABS device_21554
[13730] 0x102ee660 0x00000030 FUNC GLOB 0 ABS _info
[13731] 0x10479b38 0x00000004 OBJT GLOB 0 ABS bar2_handle
[13732] 0x10479b28 0x00000004 OBJT GLOB 0 ABS vendor_21554
……………

也就是說,當我們載入驅動程式後,其它的驅動程式也可以呼叫我們所提供的函式,這樣的特性與在Linux中撰寫驅動程式是一樣的,由於整個Solaris Kernel支援完整的動態載入機制,每個目的檔格式的驅動程式在載入核心後,就如同是整個完整核心的一部份,這些資料透過 ksyms 的動態維護,可以再我們載入新的驅動程式進行Kernel Mode的動態連結時,了解是否有未存在於Kernel中的Symbol,而發出警告,進一步的結束不完整的驅動程式載入過程。
static int pci21554_interrupt_gen()
 ddi_acc_handle_t bar2_handle;
char * bar2addr;
# /usr/ccs/bin/elfdump /dev/ksyms
……………
[5353] 0x102ef414 0x00000028 FUNC LOCL 0 ABS pb_strategy
[5354] 0x102ee3b8 0x0000006c FUNC LOCL 0 ABS pci21554_interrupt_gen
[5355] 0x102ef0e0 0x000000a4 FUNC LOCL 0 ABS pb_detach
……………
[13727] 0x102ee690 0x00000734 FUNC GLOB 0 ABS Cfg_21554_mem_space
[13728] 0x10479b34 0x00000004 OBJT GLOB 0 ABS bar2addr
[13729] 0x10479b20 0x00000004 OBJT GLOB 0 ABS device_21554
[13730] 0x102ee660 0x00000030 FUNC GLOB 0 ABS _info
[13731] 0x10479b38 0x00000004 OBJT GLOB 0 ABS bar2_handle
[13732] 0x10479b28 0x00000004 OBJT GLOB 0 ABS vendor_21554
……………
六,結語
在這篇文章的最後,我相信各位對於Solaris系統會有不錯的了解,本文主要是針對筆者認為值得深入了解的部分進行探討,其實如果要探索Solaris的核心架構,本文可能只是一個引導的角色,有許多更為深入的主題,需要透過各位
接下來的努力才會更加熟悉。
Solaris不像Linux是一個完全Open Source的作業系統,所以有很多的資訊我們無法單純的從Source Code來得知,能做的就是去反組譯一些程式碼,並自行撰寫測試程式,不過說真的,這是一個很有趣的過程,畢竟對我來說Solaris算是一個新的領域,在這之前我一直沒有機會實際的去接觸它。透過這篇文章,希望可以與各位同好分享我的經驗,若有任何意見也歡迎各位來信指教。
My E-Mailhlchou@mail2000.com.tw
七,參考文件
[2]Writing Device Driver,Sun Microsystems
[3]Solaris Internals ,Core Kernel Architecture,Jim MauroRichard McDougall
[5] Linux Kernel Internals http://www.moses.uklinux.net/lki-single.html
[6]Intel Architecture Software Developer’s ManualVolume 3System Programming
[7] The dynamic Solaris kernel , http://www.unixinsider.com/swol-02-2000/swol-02-insidesolaris.html