歡迎您光臨本站 登入註冊首頁

基於Nios軟核CPU的uC/OS-II和LwIP移植

admin @ 2014-03-26 , reply:0

概述

   Altera公司推出的Nios軟核CPU是一種可配置的通用精簡指令集計算RISC(ReducedInstructionSetComputing)嵌入式處理器。它可以……

    Altera公司推出的Nios軟核CPU是一種可配置的通用精簡指令集計算RISC(Reduced Instruction Set Computing)嵌入式處理器。它可以與各種外設相結合,構成一個定製的可編程片上系統SOPC(System on Programable Chip)。嵌入式實時操作系統uC/OS-II是一個非常優秀的實時操作系統RTOS(Real Time Operating System),其性能已得到廣泛認可。uC/OS-II的特點有:公開的源代碼、可移植、可裁剪、可固化、搶佔式內核。TCP/IP是Interenet的基本協議。嵌入式設備要與Internet網路交換信息,就必須支持TCP/IP協議。
    儘管uC/OS-II是一個開放源碼的RTOS,但是目前它的第三方TCP/IP支持都是商業化的,很少給出源代碼。用戶需要付費才能獲得。通過在Nios上移植uC/OS-II和開放源碼的TCP/IP協議棧-LwIP輕量級網路協議(Light-weight Internet Protocol),就可以實現uC/OS-II的網路功能,並建立一套嵌入式網路開發平台。該系統模型示於圖1。
 

uC/OS-II在Nios上的移植
    uC/OS-II可以看作是一個多任務的調度器,在這個任務調度器上添加了和多任務操作系統相關的一些系統服務,如信號量、郵箱、消息隊列等。uC/OS-II的設計分為與處理器類型無關的代碼、與處理器類型相關的代碼和與應用程序有關的配置代碼三部分。這也是uC/OS-II具有良好的可移植性的原因。移植工作主要集中在多任務切換的實現上。這部分代碼主要是用來保存和恢復處理器現場(即相關寄存器),因此不能用c語言,只能使用特定處理器的彙編語言完成。在Nios上移植uC/OS-II非常簡單,只需修改三個和Nios體系結構相關的文件即可。下面分別介紹這三個文件的移植工作。

1.1 OS_CPU.H文件
數據類型定義 這部分的移植是和所用的編譯器相關的,我們使用的編譯器是nios-elf-gcc。需要定義的數據類型包括無符號和有符號的8位、16位和32位整型變數等。
堆棧單位 因為處理器現場的寄存器在任務切換時都將被保存在當前運行任務的堆棧中,所以OS_STK數據類型應該與處理器的寄存器長度一致。
typedef unsigned int OS_STK;
堆棧增長方向 堆棧由高地址向低地址增長,這和選擇的編譯器有關。
#define OS_STK_GROWTH 1
宏定義(包括開、關中斷的宏定義,以及進行任務切換的宏定義)
#define OS_ENTER_CRITICAL() disable_interrupt();
#define OS_EXIT_CRITICAL() enable_interrupt()
#define OS_TASK_SW() OSCtxSw

1.2 OS_CPU_C.C文件
    該文件必須實現任務初始化時的堆棧設計,也就是在堆棧增長方向上如何定義每個需要保存的寄存器的位置。我們將堆棧空間設計為按任務堆棧空間由高至低依次保存寄存器ra、ISTATUS、r1~r31。
    該文件還需要實現幾個操作系統規定的hook函數。通常都實現為空函數。

1.3 OS_CPU A.S文件(由彙編語言實現)
(1)OSStartHighRdy()函數 此函數是在OSStart()多任務啟動后,負責從最高優先順序任務的TCB控制塊中獲得該任務的堆棧指針sp,通過sp依次將CPU現場恢復。這時系統就將控制權交給用戶創建的該任務進程,直到該任務被阻塞或者被其他更高優先順序的任務搶佔CPU。該函數僅僅在多任務啟動時被執行一次,用來啟動優先順序最高的任務執行,以後多任務的調度和切換就由下面的函數來實現。
(2)OSCtxSw()函數 任務級的上下文切換。它是當任務因被阻塞而主動請求CPU調度時被執行的。它的工作是先將當前任務的CPU現場保存到該任務堆棧中,然後獲得最高優先順序任務的堆棧指針,從該堆棧中恢復此任務的CPU現場,使之繼續執行。
(3)OSIntCtxSw()函數 中斷級的任務切換,它是在ISR(中斷服務常式)中執行任務切換。當發現有高優先順序任務就緒,則在中斷退出后並不返回被中斷任務,而是直接調度就緒的最高優先順序任務執行。這樣做的目的是能夠儘快地讓高優先順序的任務得到響應,保證系統的實時性。它的原理基本上與任務級的切換相同,但是由於進入中斷時已經保存過被中斷任務的CPU現場,因此這裡就不用再保存。
(4)OSTickISR()函數 時鐘中斷處理函數。它的主要任務是負責處理時鐘中斷,調用系統實現的OSTimeTick函數,如果有等待時鐘信號的高優先順序任務,則需要在中斷級別上調度其執行。
(5)OS_ENTER_CRITICAL()函數和OS_EXIT_CRITICAL()函數 分別是進入臨界區和退出臨界區的宏指令。主要用於在進入臨界區之前關中斷,在退出臨界區的時候恢復原來的中斷狀態。

2 LwIP
    LwIP是Light-weight Internet Protocol的縮寫,即輕量級網路協議。LwIP是瑞典計算機科學院的Adam Dunkels等開發的用於嵌入式系統的TCP/IP協議棧。LwIP實現的重點是在保持TCP/IP協議主要功能的基礎上減少對RAM的佔用,一般它只需要幾十KByte的RAM和40K左右的ROM就可以運行,適於在嵌入式系統中使用。
    在LwIP中,所有TCP/IP協議棧都在一個進程當中。應用層程序既可以是單獨的進程,也可以駐留在TCP/IP進程中。如果是單獨的進程,可以通過操作系統的郵箱、消息隊列等和TCP/IP進程進行通訊;如果駐留TCP/IP進程中,那麼利用內部回調函數介面(Raw API)和TCP/IP協議棧通訊。對uC/OS-II來說,進程就是一個任務。LwIP的進程模型 (Process Model)示於圖2。在圖2中,整個TCP/IP協議棧都在同一個任務(tcpip_thread)中。應用層程序既可以是獨立的任務(圖中的tftp_thread和cpecho_thread),也可以在tcpip_thread中利用內部回調函數介面和TCP/IP協議棧通訊。
 

3 LwIP在uC/OS-II上的移植
    在設計LwIP時,就考慮到移植問題,所有與操作系統、編譯器相關的部分被獨立出來,放在/src/arch目錄下。因此,LwIP在uC/OS-II上的實現就是修改這個目錄下的文件。下面分別說明相應文件的實現。

3.1 與CPU或編譯器相關的include文件
    在LwIP/src/arch/include/arch目錄下,cc.h、cpu.h、perf.h中有一些與CPU或編譯器相關的定義,如數據長度、字的高低位順序等。這應該與用戶實現uC/OS-II時定義的參數一致。通常,c語言的結構體(struct)是4位元組對齊的,但是在處理數據包的時候,LwIP是通過結構體中不同數據的長度來讀取相應的數據的,所以,一定要在定義struct的時候使用_packed關鍵字,讓編譯器放棄struct的位元組對齊。LwIP也考慮到了這個問題,所以,在它的結構體定義中有幾個PACK_STRUCT_xxx宏,在移植的時候添加編譯器所對應的_packed關鍵字。比如在nios-eft-gcc上對應的定義為:
#define PACK_STRUCT_FIELD(x) x_attribute_((packed))
#define PACK_STRUCT_STRUCT _attribute_((packed))

3.2 sys_arch操作系統相關部分
    sys_arch.h[c]中的內容是與OS相關的一些結構和函數,主要可以分為四個部分:
3.2.1 sys_sem_t 信號量
LwIP中需要使用信號量進行通信,所以在sys_arch中應實現信號量結構體和處理函數:
struct sys_sem_t
sys_sem_new() //創建一個信號量結構
sys_sem_free() //釋放一個信號量結構
sys_sem_signal() //發送信號量
sys_arch_sem_wait() //請求信號量
    由於uC/OS-II已經實現了信號量OS_EVENT的各種操作,並且功能和LwIP上面幾個函數的功能是完全一樣的,所以只要把uC/OS-II的函數重新封裝成上面的函數就可以了。
3.2.2 sys_mbox_t消息
    LwIP使用消息隊列來緩衝、傳遞數據報文,因此要在sys_arch中實現消息隊列結構
sys_mbox_t以及相應的操作函數:
sys_mbox_new() //創建一個消息隊列
sys_mbox_free() //釋放一個消息隊列
sys_mbox_post() //向消息隊列發送消息
sys_arch_mbox_fetch() //從消息隊列中獲取消息
    uC/OS-II雖然實現了消息隊列結構OS_Q及其操作,但是uC/OS-II沒有對消息隊列中的消息進行管理,因此不能直接使用,必須在uC/OS-II的基礎上重新實現。為了實現對消息的管理,我們定義了以下結構:
typedef struct{
OS_EVENT *pQ:
void *pvQEntries[MAX_QUEUE_ENTRIES];
}sys_mbox_t;
typedef PQ_DESCR sys_mbox_t; //LwIP中的mbox是UCOS的消息隊列
    該結構包括OS_EVENT類型的隊列指針(pQ)和隊列內的消息(pvQEntries)兩部分,對隊列本身的管理利用uC/OS-II自己的消息隊列相關函數來完成,然後使用uC/OS-II中的內存管理模塊實現對消息的創建、使用和刪除,兩部分綜合起來便實現了LwIP的消息隊列功能。
3.2.3 sys_arch_timeout函數
    LwIP中每個與外界網路連接的線程都有自己的timeout屬性,即等待超時時間。這個屬性表現為每個線程都對應一個sys_timeout結構體隊列,它包括這個線程的timeout時間長度,以及超時后應調用的timeout函數,該函數會做一些釋放連接、回收資源的工作。timeout結構體已經在sys.h中定義好了,而且對結構體隊列的數據操作也由LwIP負責,我們所要實現的是如下函數:
struct sys_timeouts *sys_arch_timeouts(void)
    這個函數的功能是返回目前正處於運行狀態的線程所對應的timeout隊列指針。timeout隊列屬於線程的屬性,因此是與操作系統相關的函數,只能由用戶實現。
3.2.4 sys_thread_new創建新線程函數
    LwIP可以是單線程運行,即只有一個tcpip線程(tcpip_thread),負責處理所有的TCP(Transmission Control Protocol:傳輸控制協議)或UDP(User Datagram Protocol:用戶數據報協議)連接,各種網路程序都通過tcpip線程與網路交互。它也可以多線程運行,以提高效率。這時就需要用戶實現創建新線程的函數:
void sys_thread_new(void(*thread)(void *arg),void *arg);
    在uC/OS-II中,沒有線程(thread)的概念,只有任務(Task)。它提供了創建新任務的系統調用OSTaskCreate,因此只要把OSTaskCreate封裝一下,就可以實現sys_thread_new。需要注意的是LwIP中的thread並沒有uC/OS-II中優先順序的概念,實現時,用戶要事先為LwIP中創建的線程分配好優先順序。

3.3 lib_arch中庫函數的實現
    LwIP用到8個外部函數,這些函數通常與用戶使用的系統或編譯器有關,因此要求用戶實現。
u16_t htons(u16_t n);//16位數據高低位元組交換
u16_t ntons(u16_t n);
u32_t htonl(u32_t n);//32位數據大小端對調
u32_ t ntonl(u32_t n);
int strlen(const char *str);
int strncmp(const char *str1,const char *str2,int len);
void bcopy(const void *src,void dest,int len);
void bzero(void *data,int n);

4 網路設備驅動程序
    我們採用的網路晶元為Cirrus Logic公司的CS8900晶元。LwIP的網路驅動有一定的模型,/src/netif/ethernetif.c文件即為驅動的模板,用戶為自己的網路設備實現驅動時應參照此模板。在LwIP中可以有多個網路介面,每個網路介面都對應了一個netif結構,該結構體包含了相應網路介面的屬性、收發函數。LwIP調用netif的函數netif->input()及netif->output()進行乙太網packet的收、發等操作。在驅動中主要做的就是實現網路介面的收、發、初始化以及中斷處理函數。
void ethernetif_init(struct netif *netif) //網卡初始化函數
void ethernetif_input(struct netif *netif) //網卡接收函數
err_t ethernetif_output(struct netif *netif,struct pbuf *p,struct ip_addr *ipaddr) //網卡發送函數
void ethernetif_isr(void);//網卡中斷處理函數

5 測試
    完成上面的移植修改工作后,就可以在uC/OS-II中初始化LwIP,並創建TCP或UDP任務進行測試了。這部分是用c語言實現的。關鍵部分的代碼和說明如下:
main(){
OSInit();
OSTaskCreate(lwip_init_task,&task1_data,&lwip_init_stk[TASK_STK_SIZE-1],0);
OSTaskCreate(user_task,&task2_data,&user_stk[TASK_STK_SIZE-1],1);
OSStart();
}
    主程序中,創建了Lwip_init_task初始化LwIP任務(優先順序0)和user_task用戶任務(優先順序1)。lwip_init_task任務中除了初始化硬體時鐘和LwIP之外,還創建了tcpip_thread(優先順序3)和tcpecho_thread(優先順序4)。實際上tcpip_thread才是LwIP的主線程,多線程的Berkley API也是基於這個線程實現的,即上面的tcpecho_thread線程也要依靠tcpip_thread線程來與外界通信。tcpecho_thread是一個TCP echo服務,監聽7號埠。程序框架如下:
void tcpecho_thread(void arg){
conn = netconn_new(NETCONN_TCP); //建立新的連接
netconn_bind(conn,NULL,7); //綁定到7號埠
netconn_listen(conn); //監聽7號埠
while(1){
newconn = netconn_accept(conn); //接收外部連接
bur=netconn_recv(newconn) //獲取數據
…… //處理數據
netconn_write(newconn,data,len,NETCONN_COPY);//發送數據
netconn_delete(newconn);//釋放本次連接
}
}
    編譯下載運行,用ping ip地址命令可以得到ICMP reply響應,用telnet ip地址7(登錄7號埠)命令可以看到echo server的回顯效果。說明ARP、IP、ICMP、TCP協議都已正確運行。

6 結論
    本文提出了在Nios軟核CPU上移植嵌入式實時操作系統uC/OS-II,並且在uC/OS-II上移植LwIP,以構建嵌入式網路開發平台。在該網路平台的設計中,一方面,由於採用Nios軟核CPU,而Nios開發軟體SOPC Builder開發環境的完備功能使得硬體設計簡單而快捷;另一方面,由於uC/OS-II和LwIP都是開放源代碼的,並且在設計時就已經考慮了移植問題,因此使得移植很方便。通過測試,證明這種方案切實可行。


[admin via 研發互助社區 ] 基於Nios軟核CPU的uC/OS-II和LwIP移植已經有2949次圍觀

http://cocdig.com/docs/show-post-43073.html