onps棧移植說明(1)——onps棧的設定及裁剪

2022-11-04 15:00:15

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