onps棧移植說明(1)——onps棧的配置及裁剪

       onps棧的移植涉及幾個部分:1)系統配置及裁剪;2)基礎數據類型定義;3)RTOS適配層實現;4)編寫網卡驅動並註冊網卡。本文作為onps棧移植的指導性文件將給出一般性的移植說明及建議,具體的移植樣例工程及說明請移步碼雲下載:

關於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宏用於指定系統緩存的路由條目數量,你可以根據實際網絡情形調整這個值,但不能低於目標系統註冊的網卡數量。協議層相關的其它配置項請根據注釋自行依據實際情況進行調整即可。