onps棧移植說明(1)——onps棧的配置及裁剪
onps棧的移植涉及幾個部分:1)系統配置及裁剪;2)基礎數據類型定義;3)RTOS適配層實現;4)編寫網卡驅動並註冊網卡。本文作為onps棧移植的指導性文件將給出一般性的移植說明及建議,具體的移植樣例工程及說明請移步碼雲下載:
- 《onps網路協議棧移植及使用說明v1.0》
- 目標os為rt-thread的移植樣例工程(STM32F407VET6及STM32F103RCT6兩個平台)
- 目標os為ucos-ii的移植樣例工程(硬體平台同上)
關於onps棧的前世今生請移步上一篇博文《開源網路協議棧onps誕生記》。
1. onps棧的配置及裁剪
協議棧源碼(碼雲/github)port/include/port/sys_config.h文件是協議棧的配置文件。它提供了一系列的配置宏用於裁剪、配置協議棧。我們可以根據目標系統的具體情況對協議棧進行裁剪,調整配置,以減少或增加對系統資源的佔用率。配置文件主要涉及幾方面的內容:
1)打開或關閉某個功能模組;
2)指定mmu(記憶體管理單元)管理的記憶體大小;
3)協議層相關配置項,如預設ttl值、路由表大小、arp快取表大小等;
onps棧在數據鏈路層支援兩種類型的網路介面:ethernet有線乙太網絡介面;ppp點對點撥號網路介面。用戶必須選擇其中至少一個介面:
#define SUPPORT_PPP 1 //* 是否支援ppp:1,支援;0,不支援
#define SUPPORT_ETHERNET 1 //* 是否支援ethernet:1,支援;0,不支援
注意,你的目標系統要麼支援ppp,要麼支援ethernet,要麼二者都支援,不能兩個都選擇不支援,否則協議棧將無法正常工作。另外協議棧還提供了幾個常用的網路工具供用戶選擇使用,用戶可以根據具體應用情形選擇打開或關閉相關工具:
//* 網路工具配置項,0:不支援;1:支援,協議棧將編譯連接工具程式碼到目標系統
//* ===============================================================================================
#define NETTOOLS_PING 1 //* ping工具,確定目標網路地址是否能到達
#define NETTOOLS_DNS_CLIENT 1 //* dns查詢客戶端,通過指定的dns伺服器查詢請求域名對應的ip地址
#define NETTOOLS_SNTP 1 //* sntp客戶端,通過指定的ntp伺服器進行網路校時
//* ===============================================================================================
考慮協議棧的目標系統可能無法提供pc下常見的文件存儲系統,所以協議棧的調試日誌等資訊是通過標準輸出提供的:
#define SUPPORT_PRINTF 1 //* 是否支援調用printf()輸出相關調試或系統資訊
#if SUPPORT_PRINTF
#define PRINTF_THREAD_MUTEX 1 //* 是否支援使用printf執行緒互斥鎖,確保不同執行緒的調試輸出資訊不被互相干擾,值為1則支援互斥鎖
#define DEBUG_LEVEL 1 //* 共5個調試級別:
//* 0 輸出協議棧底層嚴重錯誤
//* 1 輸出所有系統錯誤(包括0級錯誤)
//* 2 輸出協議棧重要的配置、運行資訊,同時包括0、1級資訊
//* 3 輸出網卡的原始通訊通訊報文(ppp為收發,ethnernet為發送),以及0、1、2級資訊
//* 4 輸出ethernet網卡接收的原始通訊報文,被協議棧丟棄的非法(校驗和錯誤、通訊鏈路不存在等原因)通訊報文,以及0、1、2、3級資訊(除ethernet發送的原始報文)
#endif
基本上所有單片機系統均會提供幾個串列口,我們只需選擇其中一個將其作為printf函數的標準輸出口,我們就可以使能協議棧支援日誌輸出功能,通過printf()函數輸出的日誌資訊對目標系統進行調試。如果你的目標系統支援某個串口作為printf()函數的標準輸出口,建議將SUPPORT_PRINTF宏置1,打開協議棧的日誌輸出功能。PRINTF_THREAD_MUTEX宏用於解決多執行緒環境下日誌輸出的衝突問題。如果你的目標系統互斥資源夠用,建議打開該功能,否則你在標準輸出口看到的日誌會出現亂序問題。
協議棧在很多情形下需要動態申請不同大小的記憶體以供接下來的邏輯處理過程使用。所以,為了最大限度地提高協議棧運行過程中的記憶體利用率並儘可能地減少記憶體碎片,我們還單獨設計了一個獨立的記憶體管理單元(mmu)。考慮協議棧的目標系統為資源受限的單片機系統,這種系統的記憶體資源往往都是極度緊張的,因此我們提供了配置宏讓用戶決定分配多少位元組的記憶體空間給協議棧的mmu:
//* 記憶體管理單元(mmu)相關配置項,其直接影響協議棧能分配多少個socket給用戶使用
//* ===============================================================================================
#define BUDDY_PAGE_SIZE 32 //* 系統能夠分配的最小頁面大小,其值必須是2的整數次冪
#define BUDDY_ARER_COUNT 9 //* 指定buddy演算法管理的記憶體塊數組單元數量
#define BUDDY_MEM_SIZE 8192 //* buddy演算法管理的記憶體總大小,其值由BUDDY_PAGE_SIZE、BUDDY_ARER_COUNT兩個宏計算得到:
//* 32 * (2 ^ (9 - 1)),即BUDDY_MEM_SIZE = BUDDY_PAGE_SIZE * (2 ^ (BUDDY_ARER_COUNT - 1))
//* 之所以在此定義好要管理的記憶體大小,原因是buddy管理的記憶體其實就是一塊提前分配好的靜態存儲時期的位元組型
//* 一維數組,以此來確保協議棧不佔用寶貴的堆空間
//* ===============================================================================================
協議棧的記憶體管理單元採用了buddy夥伴演算法。上述三個宏的關係參見BUDDY_MEM_SIZE宏的注釋。前面說過,mmu管理的記憶體用於協議棧的不同業務情形,其中最核心的一種業務情形就是socket,用戶分配的記憶體大小直接決定了用戶編寫網路應用時能夠申請的socket數量。如果你在申請分配一個新的socket時報ERRREQMEMTOOLARGE(The requested memory is too large, please refer to the macro definition BUDDY_MEM_SIZE)或ERRNOFREEMEM(The mmu has no memory available)錯誤,則意味著記憶體已經不夠用了,需要你增加記憶體或者檢視你的程式碼看是否存在未及時釋放的socket句柄。另外,決定記憶體利用效率的關鍵配置項是BUDDY_PAGE_SIZE宏,因為mmu分配記憶體的最小單位就是「頁」。這個宏設置單個記憶體頁的大小,單位為位元組,其值必須是2的整數次冪。如果你的通訊報文不大,建議把頁面大小調整的小一些,比如16位元組、32位元組等,以盡量減少單個頁面的空餘位元組數。
sys_config.h文件的其餘宏均為協議層相關的配置項。這其中有幾個與底層網路介面相關的配置項需要特別關註:
#define SUPPORT_PPP 1 //* 是否支援ppp模組:1,支援;0,不支援,如果選擇支援,則系統會將ppp模組程式碼加入到協議棧中
#if SUPPORT_PPP
#define APN_DEFAULT "4gnet" //* 根據實際情況在這裡設置預設APN
#define AUTH_USER_DEFAULT "card" //* ppp認證預設用戶名
#define AUTH_PASSWORD_DEFAULT "any_char" //* ppp認證預設口令
#define PPP_NETLINK_NUM 1 //* 協議棧載入幾路ppp鏈路(系統存在幾個modem這裡就指定幾就行)
#define SUPPORT_ECHO 1 //* 對端是否支援echo鏈路探測
#define WAIT_ACK_TIMEOUT_NUM 5 //* 在這裡指定連續幾次接收不到對端的應答報文就進入協議棧故障處理流程(STACKFAULT),這意味著當前鏈路已經因嚴重故障終止了
#else
#define PPP_NETLINK_NUM 0
#endif
#define SUPPORT_ETHERNET 1 //* 是否支援ethernet:1,支援;0,不支援
#if SUPPORT_ETHERNET
#define ETHERNET_NUM 1 //* 要添加幾個ethernet網卡(實際存在幾個就添加幾個)
#define ARPENTRY_NUM 32 //* arp條目快取表的大小,只要不小於區域網內目標通訊節點的個數即可確保arp定址次數為1,否則就會出現頻繁定址的可能,當然這也不會妨礙正常通訊邏輯,只不過這會降低通訊效率
#else
#define ETHERNET_NUM 0
#endif
如果目標系統需要用到ppp撥號,我們在打開協議棧對ppp模組的支援後還需要設置預設的撥號參數值,比如apn、撥號帳號及密碼等。當然你也可以不用設置,後面我們在編寫os適配層介面的時候也會設置這幾項。系統會使用os適配層的設置值代替預設值。另外協議棧在設計之初即考慮支援多路ppp同時撥號的情形,目標系統支援幾路ppp,宏PPP_NETLINK_NUM值置幾即可。SUPPORT_ECHO宏指定ppp鏈路是否啟用echo回顯探測功能。某些ppp接入服務商可能會關閉此項功能,如果你不確定,建議預設情況下關閉此功能。因為echo鏈路探測功能一旦被啟用,協議棧會每隔一小段時間發送探測報文到對端。對端如果不支援此功能會丟棄該探測報文不做任何響應,這將導致協議棧判定ppp鏈路故障,從而主動結束鏈路、重新撥號。
協議棧同樣支援多路ethernet網卡,ETHERNET_NUM宏用於指定目標系統存在幾路ethernet網卡。這裡需要特別注意的是ARPENTRY_NUM宏,這個宏用於指定ethernet網路環境下進行通訊時mac地址快取表的大小。如果快取表過小,進行通訊的目標地址並不在快取表中時,協議棧會先發送arp查詢報文,得到對端的mac地址後才會發送實際的通訊報文。雖然這一切都是協議棧自動進行的,但通訊效率會受到影響。如果目標系統的記憶體夠用,建議放大快取表的容量,最合理的大小是等於計劃通訊的目標地址的數量。
其餘協議層相關的配置項均屬於ip及其支援的上層協議:
//* ip支援的上層協議相關配置項
//* ===============================================================================================
#define SUPPORT_IPV6 0 //* 是否支援IPv6:1,支援;0,不支援
#define SUPPORT_SACK 0 //* 系統是否支援sack項,sack項需要協議棧建立發送隊列,這個非常消耗記憶體,通用版本不支援該項
#define ICMPRCVBUF_SIZE_DEFAULT 128 //* icmp發送echo請求報文時指定的接收緩衝區的預設大小,注意,如果要發送較大的ping包就必須指定較大的接收緩衝區
#define TCPRCVBUF_SIZE_DEFAULT 2048 //* tcp層預設的接收緩衝區大小,大小應是2^n次冪才能最大限度不浪費budyy模組分配的記憶體
#define TCPUDP_PORT_START 20000 //* TCP/UDP協議動態分配的起始埠號
#define TCP_WINDOW_SCALE 0 //* 窗口擴大因子預設值
#define TCP_CONN_TIMEOUT 30 //* 預設TCP連接超時時間
#define TCP_ACK_TIMEOUT 3 //* 預設TCP應答超時時間
#define TCP_MSL 15 //* 指定TCP鏈路TIMEWAIT態的最大關閉時長:2 * TCP_MSL,單位:秒
#define TCP_LINK_NUM_MAX 16 //* 系統支援最多建立多少路TCP鏈路(涵蓋所有TCP客戶端 + TCP伺服器的並發連接數),超過這個數量將無法建立新的tcp鏈路
#if SUPPORT_ETHERNET
#define TCPSRV_BACKLOG_NUM_MAX 10 //* tcp伺服器支援的最大請求隊列數量,任意時刻所有已開啟的tcp伺服器的請求連接隊列數量之和應小於該值,否則將會出現拒絕連接的情況
#define TCPSRV_NUM_MAX 2 //* 系統能夠同時建立的tcp伺服器數量
#define TCPSRV_RECV_QUEUE_NUM 64 //* tcp伺服器接收隊列大小,所有已開啟的tcp伺服器共享該隊列資源,如果單位時間內到達所有已開啟tcp伺服器的報文數量較大,應將該值調大
#endif
#define UDP_LINK_NUM_MAX 4 //* 調用connect()函數連接對端udp伺服器的最大數量(一旦調用connect()函數,收到的非伺服器報文將被直接丟棄)
#define SOCKET_NUM_MAX 16 //* 系統支援的最大SOCKET數量,如實際應用中超過這個數量則會導致用戶層業務邏輯無法全部正常運行(icmp/tcp/udp業務均受此影響),其值應大於等於TCP_LINK_NUM_MAX值
#define IP_TTL_DEFAULT 64 //* 預設TTL值
#define ROUTE_ITEM_NUM 8 //* 系統路由表數量
//* ===============================================================================================
目前協議棧暫不支援ipv6也不支援tcp sack選項(後續版本會支援),所以SUPPORT_IPV6和SUPPORT_SACK兩個宏不要做任何改動,始終為0即可。ICMPRCVBUF_SIZE_DEFAULT宏與ping工具有關,如果你不想使用ping工具可以將這個值設小一些以節省記憶體。TCP_WINDOW_SCALE宏建議不要做任何調整,對於記憶體空間有限的單片機系統tcp窗口直接使用指定值即可。TCP_ACK_TIMEOUT宏用於指定tcp報文發送到對端後等待對端回饋tcp ack報文的超時時間,單位:秒。UDP_LINK_NUM_MAX宏決定了目標系統在使用udp通訊時,能夠建立的udp客戶端的最大數量。比如目標系統需要建立5個udp客戶端,由於UDP_LINK_NUM_MAX值為4,那麼只有4個客戶端能正常調用connect()函數,第5個客戶端在調用connect()函數時會報ERRNOUDPLINKNODE(the udp link list is empty)錯誤。ROUTE_ITEM_NUM宏用於指定系統快取的路由條目數量,你可以根據實際網路情形調整這個值,但不能低於目標系統註冊的網卡數量。協議層相關的其它配置項請根據注釋自行依據實際情況進行調整即可。