2007-12-25 09:18:31

1 引言
嵌入式作業系統是嵌入式系統的靈魂,而且在同一個硬體平台上可以嵌入不同的嵌入式作業系統。比如ARM7TDMI核心,可以嵌入Nucleus、VxWorks、uClinux等作業系統。在此主要對uClinux的進行介紹,嵌入式uClinux作業系統主要有三個基本部分組成:引導程式、uClinux核心(由記憶體管理、程序管理和中斷處理等構成)和檔案系統。uClinux可以通過定制使核心小型化,還可以加上GUI(圖形使用者界面)和定制應用程式,并将其放在ROM、RAM、FLASH或Disk On Chip中啟動。由于嵌入式uClinux作業系統的核心定制高度靈活性,開發者可以很容易地對其進行按需配置,來滿足實際應用需要。又由于uClinux是源代碼公開,是以開發人員隻有了解核心原理就可以自己開發部分軟體,例如增加各類驅動程式。下面将詳細分析嵌入式作業系統uClinux。
2 嵌入式uClinux核心結構
uClinux核心結構如圖1所示:
圖1代表了核心的功能結構,與Linux基本相同,不同的隻是對記憶體管理和程序管理進行改寫,以滿足無MMU處理器的要求。uClinux是Linux 作業系統的一種,是由Linux2.0核心發展來的,是專為沒有MMU的微處理器(如ARM7TDMI、Coldfire 等)設計的嵌入式Linux作業系統。另外,由于大多數核心源代碼都被重寫,uClinux的核心要比原Linux 2.0核心小的多, 但保留了Linux 作業系統的主要優點:穩定性,優異的網絡能力以及優秀的檔案系統支援。
3 uClinux的記憶體管理
uClinux同标準Linux的最大差別就在于記憶體管理。标準Linux是針對有MMU的處理器設計的。在這種處理器上,虛拟位址被送到MMU,MMU把虛拟位址映射為實體位址。通過賦予每個任務不同的虛拟—實體位址轉換映射,支援不同任務之間的保護。對于uCLinux來說,其設計針對沒有MMU的處理器,不能使用處理器的虛拟記憶體管理技術。
uClinux不能使用處理器的虛拟記憶體管理技術(應該說這種不帶有MMU的處理器在嵌入式裝置中相當普遍)。
uClinux仍采用存儲器的分頁管理,系統在啟動時把實際存儲器進行分頁。在加載應用程式時程式分頁加載。但是由于沒有MMU管理,是以實際上uClinux采用實存儲器管理政策(real memeory management)。這一點影響了系統工作的很多方面。
uClinux系統對于記憶體的通路是直接的,(它對位址的通路不需要經過MMU,而是直接送到位址線上輸出),所有程式中通路的位址都是實際的實體位址。作業系統對記憶體空間沒有保護(這實際上是很多嵌入式系統的特點),各個程序實際上共享一個運作空間(沒有獨立的位址轉換表)。
一個程序在執行前,系統必須為程序配置設定足夠的連續位址空間,然後全部載入主存儲器的連續空間中。與之相對應的是标準Linux系統在配置設定記憶體時沒有必要保證明際實體存儲空間是連續的,而隻要保證虛存位址空間連續就可以了。此外磁盤交換空間也是無法使用的,系統執行時如果缺少記憶體将無法通過磁盤交換來得到改善。
uClinux對記憶體的管理減少同時就給開發人員提出了更高的要求。如果從易用性這一點來說,uClinux的記憶體管理是一種倒退,退回了到了UNIX早期或是Dos系統時代。開發人員不得不參與系統的記憶體管理。從編譯核心開始,開發人員必須告訴系統這塊開發闆到底擁有多少的記憶體(假如你欺騙了系統,那将在後面運作程式時受到懲罰),進而系統将在啟動的初始化階段對記憶體進行分頁,并且标記已使用的和未使用的記憶體。系統将在運作應用時使用這些分頁記憶體。
由于應用程式加載時必須配置設定連續的位址空間,而針對不同硬體平台的可一次成塊(連續位址)配置設定記憶體大小限制是不同(目前針對EZ328處理器的uClinux是128k,而針對Coldfire處理器的系統記憶體則無此限制),是以開發人員在開發應用程式時必須考慮記憶體的配置設定情況并關注應用程式需要運作空間的大小。另外由于采用實存儲器管理政策,使用者程式同核心以及其它使用者程式在一個位址空間,程式開發時要保證不侵犯其它程式的位址空間,以使得程式不至于破壞系統的正常工作,或導緻其它程式的運作異常。
從記憶體的通路角度來看,開發人員的權利增大了(開發人員在程式設計時可以通路任意的位址空間),但與此同時系統的安全性也大為下降。此外,系統對多程序的管理将有很大的變化,這一點将在uClinux的多程序管理中說明。
4 uClinux的多程序處理
uClinux沒有MMU管理存儲器,在實作多個程序時(fork調用生成子程序)需要實作資料保護。由于uClinux的多程序管理是通過vfork來實作,是以fork等于vfork。這意味着uClinux系統fork調用完成後,要麼子程序代替父程序執行(此時父程序已經sleep)直到子程序調用exit退出;要麼調用exec執行一個新的程序,這個時候将産生可執行檔案的加載,即使這個程序隻是父程序的拷貝,這個過程也不能避免。當子程序執行exit或exec後,子程序使用wakeup把父程序喚醒,使父程序繼續往下執行。
uClinux的這種多程序實作機制同它的記憶體管理緊密相關。uClinux針對沒有mmu處理器開發,是以被迫使用一種flat方式的記憶體管理模式,啟動新的應用程式時系統必須為應用程式配置設定存儲空間,并立即把應用程式加載到記憶體。缺少了MMU的記憶體重映射機制,uClinux必須在可執行檔案加載階段對可執行檔案reloc處理,使得程式執行時能夠直接使用實體記憶體。
5 uCLinux針對實時性的解決方案
uClinux本身并沒有關注實時問題,它并不是為了Linux的實時性而提出的。另外有一種Linux:RT-Linux關注實時問題。RT-Linux執行管理器把普通Linux的核心當成一個任務運作,同時還管理了實時程序。而非實時程序則交給普通Linux核心處理。這種方法已經應用于很多的作業系統用于增強作業系統的實時性,包括一些商用版UNIX系統,Windows NT等等。這種方法優點之一是實作簡單,且實時性能容易檢驗。優點之二是由于非實時程序運作于标準Linux系統,同其它Linux商用版本之間保持了很大的相容性。優點之三是可以支援硬實時時鐘的應用。uClinux可以使用RT-Linux的patch,進而增強uClinux的實時性,使得uClinux可以應用于工業控制、程序控制等一些實時要求較高的應用。
6 uClinux的開發環境
1,GNU開發套件
GNU開發套件作為通用的Linux開放套件,包括一系列的開發調試工具。主要元件:
Gcc: 編譯器,可以做成交叉編譯的形式,即在主控端上開發編譯目标上可運作的二進制檔案。
Binutils:一些輔助工具,包括objdump(可以反編譯二進制檔案),as(彙編編譯器),ld(連接配接器)等等。
Gdb:調試器,可使用多種交叉調試方式,gdb-bdm(背景調試工具),gdbserver(使用以太網絡調試)。
2, Clinux的列印終端
通常情況下,uClinux的預設終端是序列槽,核心在啟動時所有的資訊都列印到序列槽終端(使用printk函數列印),同時也可以通過序列槽終端與系統互動。
uClinux在啟動時啟動了telnetd(遠端登入服務),操作者可以遠端登入上系統,進而控制系統的運作。至于是否允許遠端登入可以通過燒寫romfs檔案系統時由使用者決定是否啟動遠端登入服務。
3, 交叉編譯調試工具
支援一種新的處理器,必須具備一些編譯,彙編工具,使用這些工具可以形成可運作于這種處理器的二進制檔案。對于核心使用的編譯工具同應用程式使用的有所不同。在解釋不同點之前,需要對gcc連接配接做一些說明:
ld(link description)檔案:ld檔案是指出連接配接時記憶體映象格式的檔案。
crt0.S:應用程式編譯連接配接時需要的啟動檔案,主要是初始化應用程式棧。
pic:position independence code ,與位置無關的二進制格式檔案,在程式段中必須包括reloc段,進而使的代碼加載時可以進行重新定位。
核心編譯連接配接時,使用ucsimm.ld檔案,形成可執行檔案映象,所形成的代碼段既可以使用間接尋址方式(即使用reloc段進行尋址),也可以使用絕對尋址方式。這樣可以給編譯器更多的優化空間。因為核心可能使用絕對尋址,是以核心加載到的記憶體位址空間必須與ld檔案中給定的記憶體空間完全相同。
應用程式的連接配接與核心連接配接方式不同。應用程式由核心加載(可執行檔案加載器将在後面讨論),由于應用程式的ld檔案給出的記憶體空間與應用程式實際被加載的記憶體位置可能不同,這樣在應用程式加載的過程中需要一個重新地位的過程,即對reloc段進行修正,使得程式進行間接尋址時不至于出錯。(這個問題在i386等進階處理器上方法有所不同)。
由上述讨論,至少需要兩套編譯連接配接工具:
1) 二進制工具(Binutils)
GNU binutils包包括了彙編工具、連結器和基本的目标檔案處理工具。對binutils包的設定定義了所需的目标檔案的格式和位元組順序。Binutils包種的工具都使用了二進制檔案描述符(BFD)庫來交換資料。通過設定檔案config.bfd,可以指定預設的二進制檔案格式(例如elf little endian)和任何工具可用的格式,見例1。
例1 在config.bfd中添加的用來指定目标二進制格式的代碼
arm-*-uClinux* | armel-*-uClinux*
tag_defvec=bfd_elf32_littlearm_vec
targ_selvecs=”bfd_elf32_bigarm_vec armcoff_little_vec armcoff_big_vec”
2) C編譯器
GNU編譯器集GCC是通過使用一種叫做“寄存器轉換語言”(RTL)的方式實作的。假定現在有一種基本的機器描述性檔案,它已經能滿足大家的需要。現在要做的僅僅是設定預設情況下使用的參數和如何将檔案組合成可執行檔案的方式。GNU的文檔提供了所有必需的資料,使得使用者可以為新型的處理器的指令集合提供支援。如果要針對體系的機器建立一個新的目标機器,那麼就必須指定預設編譯參數和定制系統的特定參數,見例2。對于特定的目标系統,可以使用TARGET_DEFAULT宏來在target.h檔案中定義編譯器的開關。目标t-makefile段指定了應該建構哪一個額外的例程和其編譯的方式。
例2 使用uClinux-arm.h來指定預設的編譯參數
#undef TARGET_DEFAULT
#define TARGET_DEFAULT(ARM_FLAG_APCS_32|ARM_FLAG_NO_GOT)
4 可執行檔案格式
先對一些名詞作一些說明:
coff(common object file format):一種通用的對象檔案格式
elf(excutive linked file):一種為Linux系統所采用的通用檔案格式,支援動态連接配接
flat:elf格式有很大的檔案頭,flat檔案對檔案頭和一些段資訊做了簡化
uClinux系統使用flat可執行檔案格式,gcc的編譯器不能直接形成這種檔案格式,但是可以形成coff或elf格式的可執行檔案,這兩種檔案需要coff2flt或elf2flt工具進行格式轉化,形成flat檔案。當使用者執行一個應用時,核心的執行檔案加載器将對flat檔案進行進一步處理,主要是對reloc段進行修正。以下對reloc段進一步讨論。
需要reloc段的根本原因是,程式在連接配接時連接配接器所假定的程式運作空間與實際程式加載到的記憶體空間不同。假如有這樣一條指令:
jsr app_start;
這一條指令采用直接尋址,跳轉到app_start位址處執行,連接配接程式将在編譯完成是計算出app_start的實際位址(設若實際位址為0x10000),這個實際位址是根據ld檔案計算出來(因為連接配接器假定該程式将被加載到由ld檔案指明的記憶體空間)。但實際上由于記憶體配置設定的關系,作業系統在加載時無法保證程式将按ld檔案加載。這時如果程式仍然跳轉到絕對位址0x10000處執行,通常情況這是不正确的。一個解決辦法是增加一個存儲空間,用于存儲app_start的實際位址,設若使用變量addr表示這個存儲空間。則以上這句程式将改為:
movl addr, a0;
jsr (a0);
增加的變量addr将在資料段中占用一個4位元組的空間,連接配接器将app_start的絕對位址存儲到該變量。在可執行檔案加載時,可執行檔案加載器根據程式将要加載的記憶體空間計算出app_start在記憶體中的實際位置,寫入addr變量。系統在實際處理時不需要知道這個變量的确切存儲位置(也不可能知道),系統隻要對整個reloc段進行處理就可以了(reloc段有辨別,系統可以讀出來)。處理很簡單,隻需要對reloc段中存儲的值統一加上一個偏置(如果加載的空間比預想的要靠前,實際上是減去一個偏移量)。偏置由實際的實體位址起始值同ld檔案指定的位址起始值相減計算出。這種reloc的方式部分是由uClinux的記憶體配置設定問題引起的。
7 總結
以上主要闡述了嵌入式作業系統uClinux的核心結構、、記憶體管理、多程序處理、針對實時性的解決方案和開發環境,先對uCLinux有一個深刻的認識,将有利于今後進一步研究開發。