LWIP使用經驗---變態級(好文章)
LWIP使用經驗
一 LWIP記憶體管理
LWIP的記憶體管理使用了2種方式:記憶體池memp和記憶體堆mem,如圖1所示。
記憶體池的特點是預先開闢多組固定大小的記憶體塊組織成連結串列,實現簡單,分配和回收速度快,不會產生記憶體碎片,但是大小固定,並且需要預估算準確。
記憶體堆的本質是對一個事先定義好的記憶體塊進行合理有效的組織和管理,主要用於任意大小的記憶體分配,實現較複雜,分配需要查詢,回收需要合併,容易產生記憶體碎片,需要合理估算記憶體堆的總大小。
圖1記憶體池與記憶體堆
1. 資料包管理
資料包管理結構pbuf共有四種類型,它們的特點和使用場合如表1所示。
類別 |
分配方式 |
特點 |
使用場合 |
PBUF_RAM |
記憶體堆,包括pbuf和資料區 |
長度不定,分配耗時 |
應用程式和協議棧 |
PBUF_POOL |
記憶體池,包括pbuf和資料區 |
長度固定,分配快 |
中斷服務程式 |
PBUF_ROM |
記憶體池,僅包括pbuf |
所指資料位於ROM中 |
應用程式引用記憶體區 |
PBUF_REF |
記憶體池,僅包括pbuf |
所指資料位於RAM中 |
應用程式引用記憶體區 |
表1 pbuf型別與特點
每一種pbuf分配記憶體的方式都不一樣,如圖2所示。
圖2四種資料包管理結構
只有選擇合適的pbuf
圖3 pbuf連結串列
2. 設定記憶體大小
為LWIP開闢一個專用的記憶體堆是應該的,這樣一來LWIP的mem_alloc()和mem_free()都將基於該堆記憶體進行分配和回收,不影響其他系統記憶體的使用。如圖1左所示,lwipopt.h檔案中巨集MEM_SIZE定義了堆區的大小,對於一個負荷較重的系統堆區需要分配較大。
圖4堆和PBUF_ROM記憶體區
LWIP使用PBUF_ROM型別的記憶體池來發送“只讀”資料(處於ROM中或者其他程序中不可修改),巨集MEMP_NUM_PBUF
在ISR(中斷服務程式中)經常需要快速分配一部分記憶體進行資料儲存,這是通過PBUF_POOL型別的緩衝區實現的。為此需要定義兩個巨集:PBUF_POOL_SIZE定義緩衝池的個數,PBUF_POOL_BUFSIZE定義單個緩衝區的大小,如圖5所示。
圖5 PBUF_POOL記憶體區
3 巨集編譯開關
若定義MEM_LIBC_MALLOC=1,直接使用C庫中的malloc、free來分配動態記憶體;否則使用LWIP自帶的mem_malloc、mem_free等函式。
若定義MEMP_MEM_MALLOC=1,則memp.c中的全部內容不會被編譯,用記憶體堆來實現記憶體池分配,使用這種方式得考慮是否能忍受記憶體堆分配帶來的時間延遲。
若定義MEM_USE_POOLS=1,則mem.c中的全部內容不會被編譯,用記憶體池來實現記憶體堆的分配,使用這種方式得考慮是否能忍受因為POOL記憶體固定大小而帶來的記憶體浪費。此外使用者需要定義巨集MEM_USE_CUSTOM_POOLS=1,還需要額外實現一個頭檔案lwippools.h,並在其中開闢一些用於記憶體堆分配函式的記憶體池POOL,開闢空間的格式如下:
LWIP_MALLOC_MEMPOOL_START
LWIP_MALLOC_MEMPOOL(20, 256)
LWIP_MALLOC_MEMPOOL(10, 512)
LWIP_MALLOC_MEMPOOL_END
二 LWIP啟動時序
圖6展示了LWIP啟動時序,大部分函式都是LWIP自帶的,主要的移植程式碼是eth_init()實現初始化乙太網介面,啟動程式會建立2個執行緒:tcpip_thread負責LWIP的絕大部分工作(主要是協議棧的解析和系統執行),ethernetif_thread負責從網口接收資料包再交付給tcpip_thread執行緒進行處理。
圖6 LWIP啟動函式
三 LWIP執行邏輯
1 接收資料包
圖7接收資料包
當乙太網口接收到一個數據包後,EMAC_IRQ中斷服務程式通過訊號量通知ethernetif執行緒,ethernetif執行緒呼叫low_level_input()接收該資料包並通過郵箱交付給tcpip_thread執行緒,tcpip_thread根據該資料包的型別進行相應處理。它是建立在訊息傳遞的基礎上的,如圖8所示。
圖8資料包訊息的產生和處理
2 SequentialAPI函式呼叫
API設計的核心在於讓使用者程序負責儘可能多的工作,例如資料的計算、拷貝等;而協議棧程序只負責簡單的通訊工作,這是很合理的,因為系統可能存在多個應用程式,它們都使用協議棧程序提供的通訊服務,保證核心程序的高效性和實時性是提高系統性能的重要保障。程序之間通訊使用IPC技術,包括郵箱、訊號量和共享記憶體,如圖9所示。
圖9協議棧API實現
以函式netconn_bind()為例看API是如何實現的,首先使用者程式中呼叫函式netconn_bind()繫結一個連線,則這個函式實現時,是通過向核心程序傳送一個TCPIP_MSG_API型別的訊息,告訴核心程序執行do_bind函式:在訊息傳送後,函式阻塞在訊號量上,等待核心處理該訊息;核心在處理訊息時,會根據訊息內容呼叫do_bind,而do_bind會根據連線的型別呼叫核心函式udp_bind、tcp_bind或raw_bind;當do_bind執行完後,它會釋放訊號量,這使被阻塞的netconn_bind得以繼續執行,整個過程如圖10所示。
圖10 API函式實現
四 TCP/IP核心知識點
1. 滑動視窗
圖11滑動視窗
接收視窗相關的欄位中,rcv_nxt是自己期望收到的下一個位元組編號,rcv_wnd表示接收視窗的大小,rcv_ann_wnd表示將向對方通告的視窗大小值,這個值在報文傳送時會被填在首部中的視窗大小欄位,rcv_ann_right_edge記錄了上一次視窗通告時視窗右邊界取值。上面這四個欄位都會隨著資料的傳送和接收動態地改變,如圖12所示。
圖12接收視窗
傳送視窗中,lastack記錄了被接收方確認的最高序列號,snd_nxt表示自己將要傳送的下一個資料的起始編號,snd_wnd記錄了當前的傳送視窗大小,它常被設定為接收方通告的接收視窗值,snd_lbb記錄了下一個被應用程式快取的資料的起始編號,如圖10所示。
上面這四個欄位的值也是動態變化的,每當收到接收方的一個有效ACK後,lastack的值就做相應的增加,指向下一個待確認資料的編號,當傳送一個報文後,snd_nxt的值就做相應的增加,指向下一個待發送資料,snd_nxt和lastack之間的差值不能超過snd_wnd的大小。
由於實際資料傳送時是按照報文段的形式組織的,因此可能存在這樣的情況:即使傳送視窗允許,但並不是視窗內的所有資料都能傳送以填滿視窗,如圖13中編號為11~13的資料,可能因為它們太小不能組織成一個有效的報文段,因此不會被髮送。傳送方會等到新的確認到來,從而使傳送視窗向右滑動,使得更多的資料被包含在視窗中,這樣再啟動下一個報文段的傳送。
圖13傳送視窗
2. 三次握手
圖14連線建立過程
3. 斷開連線
圖15連線斷開過程
4. TCP狀態轉換
圖16 TCP狀態轉換圖
5. 同時開啟
圖17雙方同時開啟
6. 同時關閉
圖18雙方同時關閉
五正確使用LWIP
一般說來LWIP協議棧是比較穩定的,尤其像V1.3.2經歷過業界廣泛使用和工程應用,完全可以應用於嵌入式產品。那為什麼還是有很多人反映LWIP不穩定呢?主要是以下幾個方面的原因:
1. 網路硬體驅動確保EMAC口接收與傳送穩定可靠
2. 移植LWIP 基於OS的移植程式碼正確穩定
3. 配置LWIP 根據裝置RAM尺寸進行合理配置
1) 值(PBUF_POOL_SIZE * PBUF_POOL_BUFSIZE)必須大於TCP_SND_BUF和TCP_WND,否則容易引起錯誤;
2) 當記憶體有限時TCP傳送不能太快(具體值依賴於分配記憶體的大小),否則引起tcp_enqueue()邏輯錯誤;
4. 呼叫LWIP的API函式正確使用API函式,特別防止記憶體洩露。
5. 資源不足開啟報警提醒,當資源不夠時提醒設計者
六 LWIP常見問題
1. 網絡卡驅動程式
首先,必須將協議棧完全初始化才能開啟網路接收功能,接收中斷必須將資料封裝在PBUF中,然後交會給協議棧核心處理。其次,LWIP的全域性變數(arp_table,netif_list,udp_pcbs等)確保賦初值0,否則容易一執行就崩潰。
2. 記憶體洩露
第一個原則(責任制):誰分配記憶體,誰就負責回收。
第二個原則(對稱性):分配記憶體者與回收記憶體者一一對應構成閉環。
另外,需要特別注意一些系統函式的呼叫,它們也會帶來記憶體洩露,如:
例1
newconn = netconn_accept(conn);
do_something_for(newconn);
netconn_close(newconn);
netconn_delete(newconn); /*一定要釋放newconn否則將導致記憶體洩露 */
例2
inbuf = netconn_recv(conn);
do_something_for(inbuf);
netbuf_delete(inbuf); /*一定要釋放inbuf否則將導致記憶體洩露 */
3. PC機無法與LWIP建立TCP連線
問題:PC機能夠與LWIP裝置PING操作成功,但是無法建立TCP連線。
原因:通過程式碼跟蹤,發現LWIP發出了SYN+ACK資料包,但是PC機無法接收該握手資料包,該資料包為60位元組,小於乙太網的最小長度(64位元組),而LWIP裝置的EMAC沒有設定短小資料包填充功能,導致PC機無法接收該短資料包。
解決:使能EMAC的短小資料包填充功能。