Newlib函式庫的優勢利用

Newlib 是一個用於嵌入式系統的開放源程式碼的C語言程序庫。特點是輕量級,速度快,可移植到很多CPU結構上。

Newlib函式庫是一個複雜的標準C 函式庫,包括字符串支持,浮點運算,內存分配(malloc),和I/O流函數(printf,fprinf(),等等)。Newlib有兩個組件構成:libc提供了主要的C 語言函式庫的實現,而libm提供了浮點運算支持。

為什麼要為Newlib費心呢?如果你正在寫一個非常小的嵌入式系統,你可能有自己的C標準函式庫的子集。但是如果你要求很多的基本函數,那麼一個現存的庫能夠幫你跳過大量的工作而使你集中力量在真正的任務上。這就是Newlib的價值,你可以從Newlib中的得到100,000多行的預先寫好的經過測試的函式庫程式碼。

儘管Newlib 提供了複雜的函數集,你不用擔心它脹滿你的ROM。程式碼是非常模塊化的,所以你的目標程式碼連接器將在需要的時候從庫中呼叫相應的功能。
發布與授權許可

Newlib函式庫的發布是源程式碼級的,你在嵌入式應用中使用它前需要交叉編譯生成二進制庫。由於主要使用GNU GCC交叉編譯器工具鏈,所以使用GCC配置和生成二進制庫是一個簡單的處理過程。你也可以使用非GCC工具,但需要更多的手工配置。

Newlib 包括了從各種資源中收集來的程式碼,多個免費軟件授權的分發也反映了其起源的多樣性。基本上它有一個嵌入式系統開發者所喜愛的像BSD一樣的非限制性授權。允許你不需要公開你的源程式碼來使用該庫。你可以從newlib home page 得到更詳細的信息。
系統呼叫例程

Newlib依賴於少量的系統呼叫例程,你必須對依賴於系統的任務提供處理,特別是I/O支持。例如,你呼叫了printf()函數,Newlib函式庫負責創建一個格式化的字符串,但是不知道在那裡或如何顯示它。因此它需要呼叫_write系統例程來執行最後輸出。

    int _write(int handle, char *ptr, size_t len);

當呼叫printf()的時候,」handle」將被保留其中」1」意味著stdout標準輸出,」ptr」指向包含格式化字符串的緩衝區,」len」將是格式化字符串的長度。你的_write()系統呼叫例程的實現是通過一個串行調試端口發送緩衝區內容到一個遠端的調試終端上顯示出來。

下面是Newlib可能需要的系統呼叫例程的列表。不要被這個列表嚇住:很少需要你來實現所有這些呼叫。而且經常是返回-1來指示一個錯誤或者一個合適的偽結果。

    * _sbrk: 改變堆的分配(對 malloc而言)
    * _open: 打開文件(基於句柄的)
    * _close: 關閉文件
    * _write: 寫文件
    * _read: 讀文件
    * _lseek: 重新定向文件中的位置
    * _fcntl: 執行一個文件描述符的操作
    * _fstat: 得到文件狀態的句柄
    * _stat: 按名稱得到文件狀態
    * _link: 生成一個文件鏈接(文件命名)
    * _unlink: 刪除目錄項
    * _times: 讀取時間信息
    * _gettimeofday: 得到時間日期
    * _execve: 執行一個文件
    * _kill: 殺死一個進程
    * _getpid: 得到進程標識
    * _fork: 創建一個新的進程
    * _wait: 等待子進程的終止

實現系統呼叫例程的最簡單方法是按照需要來實現。直接使用Newlib函式庫,直到目標程式碼連接器警告一個庫函數缺少了一個系統呼叫函數再去實現它。通過這樣的方法,你就不會為那些你的系統不需要的系統呼叫浪費時間了。

接下來,我們來看一下 Newlib函式庫中的文件流I/O函數,並且討論在一個實時操作系統環境中使用Newlib函式庫的重進入和多線程問題。

在多線程環境中使用Newlib函式庫

Newlib提供了一個複雜的函數集,使用它們能夠讓你集中精力在真正的工作上。
我還提供了當使用newlib時需要實現的系統呼叫例程列表。現在,讓我們看一下Newlib函式庫中的文件流I/O函數,並且討論在一個多線程實時操作系統環境中如何安全使用newlib苦。
文件和I/O流支持

文件流I/O函數,例如fopen(), fread(), fwrite(), fprintf(), 和fclose(), 在Newlib函式庫中很容易被混淆。這裡有一個固有的文件系統嗎?沒有。在你所提供的基於句柄系統呼叫例程_open(), _read(), _write(), 和 _close()的頂部,這些newlib函數隻提供了緩衝區的流I/O層。

這裡的基本原理是你能夠像在UNIX/POSIX中對待文件那樣處理I/O設備。你的系統呼叫例程能夠將文件I/O請求定向到你想要的地方:串口,LCD顯示屏,塊存儲設備,文件系統驅動器等等。

如果你有多個設備,當你通過_open().打開一個設備時需要返回一個唯一的句柄。然後函數_read(), _write(), 和 _close()通過這個句柄參數決定將一個請求定向到哪一個設備上。

不要忽視這一工具,不要低估這種機制的力量。
在多線程環境中使用newlib

C的標準函式庫是天生沒有重進入點的。如果沒有顧慮的這一點,它將導致在多線程實時操作系統環境中的使用不安全。這是因為某些函數需要靜態數據來保持呼叫的狀態信息,而其他函數依賴errno靜態變量來報告錯誤程式碼。如果多線程中的函數呼叫修改了相同的靜態數據,將發生致命的混淆。

幸運的是,Newlib函式函式庫程式碼已經涵蓋了這一點。沒有在簡單的存儲位置存儲它的靜態變量,Newlib函式庫定義了 _reent結構封裝了庫中需要的所有靜態變量。一個單一的指針(_impure_ptr)指向了_reent數據塊。Newlib函式庫中的所有函數都使用了_impure_ptr 指向的_reent數據塊中的「靜態「數據。

這種機制的背後是這樣一種思想,在一個多線程環境中,每個任務必須由它自己的_reent數據塊。進一步,無論實時操作系統什麼時候執行一個任務切換,你必須為這個新激活的任務改變指向的_reent數據塊的_impure_ptr指針。

如果在一個多線程環境中運行Newlib函式庫,要注意兩點:

   1. 為每一個任務分配一個_reent結構
   2. 在實時操作系統上下文切換中,為這個新激活的任務改變指向的_reent數據塊的_impure_ptr指針

如果在你的實時操作系統裡有合適的系統呼叫,你通常很容易的實現這些(更容易的是你有實時操作系統的源程式碼)。

系統默認地為單線程環境保留一個的_impure_ptr指針指向的_reent結構實例。所以,如果你不運行多線程應用,你不用考慮這些問題。

當你寫嵌入式應用的程式碼時,你也不需要明白這種機制。例如,可以按照往常那樣修改標準C的errno變量,如下所示:

errno = 0;

但是在這種情形的背後,errno被一個宏定義實現成以下程式碼,所以每個人物猶他自己的私有errno變量。

_impure_ptr->errno = 0
保護newlib的堆管理器

在多線程環境中,使用malloc() 和free()函數可以保護對newlib所管理的系統堆的訪問。不要讓兩個以上的任務同時操縱堆。

在強調一下,newlib使在訪問對管理結構前後呼叫_malloc_lock() 和 _malloc_unlock()系統例程變得簡單,本著使用實時操作系統的互斥機制保證在同一時間只有同一任務訪問堆的原則,來實現_malloc_lock() 和 _malloc_unlock()就可以了。

不要跳過這一步。即使嵌入式應用中沒有明確的呼叫malloc(),Newlib函式庫內部有時也會自己使用malloc()。

arrow
arrow
    全站熱搜

    Bluelove1968 發表在 痞客邦 留言(0) 人氣()