複製鏈接
請複製以下鏈接發送給好友

newlib

鎖定
Newlib是一個面向嵌入式系統的C運行庫。最初是由Cygnus Solutions收集組裝的一個源代碼集合,取名為newlib,現在由Red Hat維護,最新的版本是2.1.0。
對於與GNU兼容的嵌入式C運行庫,Newlib並不是唯一的選擇,但是從成熟度來講,newlib是最優秀的。newlib具有獨特的體系結構,使得它能夠非常好地滿足深度嵌入式系統的要求。newlib可移植性強,具有可重入特性、功能完備等特點,已廣泛應用於各種嵌入式系統中。 [1] 
外文名
Newlib
性    質
面向嵌入式系統的C運行庫
版    本
2.1.0
特    點
可移植性強,具有可重入特性等

newlib移植

Newlib的所有庫函數都建立在20個樁函數的基礎上[2],這20個樁函數完成一些newlib無法實現的功能:
(1) 級I/O和文件系統訪問(open、close、read、write、lseek、stat、fstat、fcntl、link、unlink、rename);
(2) 擴大內存堆的需求(sbrk);
(3) 獲得當前系統的日期和時間(gettimeofday、times);
(4) 各種類型的任務管理函數(execve、fork、getpid、kill、wait、_exit);
這20個樁函數在語義、語法上與POSIX標準下對應的20個同名系統調用是完全兼容的[3]。成功移植newlib的關鍵是在目標系統環境下,找到能夠與這些樁函數銜接的功能函數並實現這些樁函數。
Newlib為每個樁函數提供了可重入的和不可重入的兩種版本。兩種版本的區別在於,如果不可重入版樁函數的名字是xxx,則對應的可重入版樁函數的名字是_xxx_r,如close和_close_r,open和_open_r,等等。此外,可重入的樁函數在參數表中含有一個_reent結構指針,這個指針使得系統的實現者能在庫和目標操作環境之間傳送上下文相關的信息,尤其是發生錯誤時,能夠便捷的傳送errno的值到適當的任務中。
所謂最小實現是指,假定將要移植的目標系統中沒有文件系統,也沒有符合POSIX標準的任務管理機制和應用編程接口(Application Programming Interface, API),僅僅實現newlib的一個最小移植。在newlib的移植過程中全功能實現的樁函數只有open、close、read、write和sbrk五個,其他樁函數僅僅實現一個返回錯誤的空函數。
任務管理的execve、fork、getpid、kill、wait和_exit六個樁函數,僅僅實現一個返回-1的空函數,返回之前將errno設置為ENOTSUP,表示系統不支持該函數。
與文件相關的link和unlink樁函數也僅僅實現一個返回-1的空函數,將errno設置為EMLINK表示連接過多;lseek函數則不需要返回任何錯誤,直接返回0,表示操作成功。
fstat和stat樁函數在newlib中主要用於判斷流的類型(常規文件、字符設備、目錄),將其實現為不論輸入參數如何,都返回字符設備類型的空函數。
times樁函數返回當前進程中的各種時間信息,如果目標系統中的任務不能提供類似的時間信息,僅僅實現一個返回-1的空函數,將errno設置為ENOTSUP。
由於newlib認為在目標系統中fcntl、rename和gettimeofday三個樁函數缺省是不提供的,所以也不提供這三個樁函數的實現。

newlib程序特點

C運行庫的可重入性問題主要是庫中的全局變量在多任務環境下的可重入性問題,Newlib解決這個問題的方法是,定義一個struct _reent類型的結構,將運行庫所有會引起可重入性問題的全局變量都放到該結構中。而這些全局變量則被重新定義為若干個宏,以errno為例,名為“errno”的宏引用指向struct _reent結構類型的一個全局指針,這個指針叫做_impure_ptr。
對於用户,這一切都被errno宏隱藏了,需要檢查錯誤時,用户只需要像其他ANSI C環境下所做的一樣,檢查errno“變量”就可以了。實際上,用户對errno宏的訪問是返回_impure_ptr->errno的值,而不是一個全局變量的值。
Newlib定義了_reent結構類型的一個靜態實例,並在系統初始化時用全局指針_impure_ptr指向它。如果系統中只有一個任務,那麼系統將正常運行,不需要做額外的工作;如果希望newlib運行在多任務環境下,必須完成下面的兩個步驟:
(1) 每個任務提供一個_reent結構的實例並初始化;
(2) 任務上下文切換的時刻重新設置_impure_ptr指針,使它指向即將投入運行任務的_reent結構實例。
這樣就可以保障大多數庫函數(尤其是stdio庫函數)的可重入性。如果需要可重入的malloc,還必須設法實現__malloc_lock()和__malloc_unlock()函數,它們在內存分配過程中保障堆(heap)在多任務環境下的安全。

newlib函數實現

I/O實現
Newlib在使用open、close、read和write樁函數時嚴格遵守POSIX標準,為了使實現的樁函數完全符合POSIX,就必須在內部機制上實現設備名錶、文件描述符表和驅動地址表3個表的相關操作。
4.1 三個表的結構、作用及相關操作
(1) 設備名錶記錄系統中所有設備的名字及其設備號。系統初始化時必須將所有的設備名及其設備號填入表中備查。
對於設備名錶應該實現以下兩個操作:
(1) 設備名/設備號註冊函數NameRegister;
(2) 從設備名到設備號的轉換函數NameLookup;
(2) 文件描述符表記錄系統中當前打開的設備的設備號。每個表項代表一個處於打開狀態的設備。每個表項的索引值就是需要返回給用户的文件描述符
文件描述符表需要實現以下3個操作:
(1) 文件描述符分配函數FdAllocate;
(2) 文件描述符釋放函數FdFree;
(3) 從文件描述符到設備號的轉換函數Fd2DevCode;
(3) 驅動地址表記錄系統中每個驅動程序的入口地址。每個表項代表一個驅動程序,對每個驅動程序都應該實現五個具有統一接口的操組函數:init、open、close、read、write。每個表項在表中的索引值就是該設備的設備號。需要注意是每個驅動程序都必須提供init操作。
對驅動地址表需要實現以下操作:
初始化驅動表中的所有驅動函數InitAllDrivers;
該操作對錶中的每一個驅動程序調用init操作,完成表中所有驅動程序的初始化操作。
系統初始化的時間,應該調用InitAllDrivers()操作,完成系統中所有驅動程序的初始化操作。在每個驅動程序的init操作中,應該調用NameRegister()操作,完成驅動程序對應的設備註冊,以COM1驅動程序的com1_init()操作為例,它的實現如下:
void com1_init(int devCode)
{
/*首先註冊設備名和設備號到設備名錶中*/
NameRegister(“COM1”, devCode);
/*然後完成其他的設備初始化操作*/
}
只要所有的設備驅動程序都遵守這個約定,在系統初始化完成之後,系統中所有的驅動程序就得到了初始化,並且系統中所有的設備都註冊到了設備名錶中。後續的I/O樁函數的實現就非常容易了。
open 樁實現
open樁函數的實現流程如下:
(1) 用NameLookup()操作在設備名錶中搜索匹配的設備名,並獲得對應的設備號;
(2) 用FdAllocate()操作從文件描述符表中分配一個空的表項,填入設備號,並獲得對應的索引號即fd;
(3) 通過設備號直接調用驅動地址表中對應驅動程序的open操作;
(4) 返回fd。
4.3 read、write和close樁函數的實現
read和write樁函數的實現方法完全相同,流程如下:
(1) 調用Fd2DevCode()操作獲得與輸入參數fd對應的設備號devCode;
(2) 通過設備號直接調用驅動地址表中對應驅動的read或write操作;
(3) 返回實際交換的數據量。
close樁函數的實現與read、write幾乎完全相同,唯一不同之處在於最後調用FdFree()操作,釋放fd而不是返回實際交換的數據量,流程如下:
(1) 調用Fd2DevCode()操作獲得與輸入參數fd對應的設備號devCode;
(2) 通過設備號直接調用驅動地址表中對應驅動的close操作;
(3) 調用FdFree()操作釋放fd。
至此,與設備I/O相關的四個樁函數open、close、read和write的實現就全部完成了。
本文沒有介紹驅動程序的實現方法,並不是驅動程序不重要,恰恰相反,驅動程序中必須完成可靠高效的設備操作,保證驅動程序的各項操作在語義上與上面4個樁函數完全一致,並且實質性的操作都在驅動程序中完成。因此,在驅動程序的實現上必須仔細斟酌。由於篇幅的原因,不再贅述。

newlib關於malloc

大多數嵌入式操作系統都實現了自己的動態內存分配機制,並且提供了多任務環境下對內存分配機制的保護措施,如果移植newlib到這樣的系統時,可以放棄newlib自帶的malloc函數。儘管newlib自帶的malloc非常高效,但是幾乎所有的用户都習慣使用malloc來作為動態內存分配器。在這種情況下,最好對系統自帶的動態內存分配API進行封裝,使它不論在風格、外觀上,還是在語義上都與malloc完全相同,這對於提高應用程序的可移植性大有好處。
對於那些沒有實現動態內存分配機制的嵌入式系統環境來説,newlib的malloc是一個非常好的選擇,只需實現sbrk樁函數,malloc就可以非常好地工作起來。與之同名的POSIX系統調用的作用是從系統中獲得一塊內存,每當malloc需要更多的內存時,都會調用sbrk函數。
在單任務環境下,只需實現sbrk樁函數,malloc就可以正常運行;但在多任務環境下,還需實現__malloc_lock()和__malloc_unlock()函數,newlib用這兩個函數來保護內存堆免受衝擊。用户可利用目標環境中的互斥信號量機制來實現這兩個函數,在__malloc_lock()函數中申請互斥信號量,而在__malloc_unlock()函數中釋放同一個互斥信號量。
[2] 
參考資料