協定棧原始碼(碼雲/github)port/include/port/datatype.h中根據目標系統架構(16位元 or 32位元)及所使用的編譯器定義基礎資料型別及位元組對齊方法。這個檔案中最重要的移植工作就是依據目標編譯器手冊定義位元組對齊方法。因為網路協定棧最關鍵的地方就是底層通訊報文結構必須位元組對齊,而不是通常情形下的預設四位元組對齊。
#define PACKED __attribute__((packed)) //* 預設提供了gcc編譯器的位元組對齊方法
#define PACKED_FIELD(x) PACKED x
#define PACKED_BEGIN
#define PACKED_END
協定棧原始碼提供了常用的gcc編譯器的位元組對齊方法。PACKED宏及PACKED_BEGIN/PACKET_END組合體宏通常用於結構體位元組對齊定義。二者選其一實現即可。PACKED_FIELD宏用於定義單個變數位元組對齊。注意,位元組對齊定義是整個協定棧能否正常運轉的關鍵。所以,必須確保該定義能正常工作。
協定棧原始碼提供了32位元系統下的基礎資料型別定義樣例,具體移植時可參考該樣例進行調整:
//* 系統常用資料型別定義(不同的編譯器版本,各資料型別的位寬亦不同,請根據後面註釋選擇相同位寬的型別定義)
typedef unsigned long long ULONGLONG; //* 64位元無符號長整型
typedef long long LONGLONG; //* 64位元有符號長整型
typedef signed long LONG; //* 32位元的有符號長整型
typedef unsigned long ULONG; //* 32位元的無符號長整型
typedef float FLOAT; //* 32位元的浮點型
typedef double DOUBLE; //* 64位元的雙精度浮點型
typedef signed int INT; //* 32位元的有符號整型
typedef unsigned int UINT; //* 32位元的無符號整型
typedef signed short SHORT; //* 16位元的有符號短整型
typedef unsigned short USHORT; //* 16位元的無符號短整型
typedef char CHAR; //* 8位元有符號位元組型
typedef unsigned char UCHAR; //* 8位元無符號位元組型
typedef unsigned int in_addr_t; //* internet地址型別
其中in_addr_t比較特殊,用於socket程式設計,其為IPv4地址型別,其必須是無符號4位元組整型數。
對於 os 適配層,主要的移植工作就幾塊:1)提供多工(執行緒)建立函數;2)提供系統級的秒級、毫秒級延時函數及執行時長統計函數;3)提供同步(互斥)鎖相關操作函數;4)提供號誌操作函數;5)提供一組臨界區保護也就是中斷禁止/使能函數。os 適配層的移植工作涉及 os_datatype.h、os_adapter.h、os_adapter.c 三個檔案。
這個檔案負責完成與目標作業系統相關的資料型別定義,主要就是互斥鎖、號誌、tty這三種資料型別的定義。互斥鎖用於執行緒同步,號誌用於執行緒間通訊,tty則用於ppp模組。我們需要在這個檔案裡定義能夠唯一的標識它們的存取控制程式碼供協定棧使用。
typedef INT HMUTEX; //* 執行緒互斥(同步)鎖控制程式碼
#define INVALID_HMUTEX -1 //* 無效的執行緒互斥(同步)鎖控制程式碼
#if SUPPORT_PPP
typedef INT HTTY; //* tty終端控制程式碼
#define INVALID_HTTY -1 //* 無效的tty終端控制程式碼
#endif
typedef INT HSEM; //* 號誌,適用與不同執行緒間通訊
#define INVALID_HSEM -1 //* 無效的號誌控制程式碼
注意,上面給出的只是一般性定義,使用時請依據目標os的實際情形進行調整。另外,如果你的目標系統不需要ppp模組,HTTY及INVALID_HTTY無須定義。
原始碼工程提供的os_datatype.h檔案為樣例檔案。基於協定棧的通用性考慮,樣例檔案提供的與os相關的資料型別定義存在冗餘。除上述三種資料型別必須定義外,其它預留的型別如目標系統已提供,建議直接使用目標系統的定義,os_datatype.h檔案中的冗餘定義直接註釋掉即可;如不存在,則直接使用樣例檔案中的通用定義即可。
協定棧業務邏輯的完成離不開os的支援,這個檔案的主要作用就是提供與os相關的介面函數宣告,然後在os_adapter.c中實現這些函數。所以,這個檔案中要調整的地方並不多,只有兩處。一個是協定棧內部工作執行緒控制塊:
typedef struct _STCB_PSTACKTHREAD_ { //* 協定棧內部工作執行緒控制塊,其用於執行緒建立
void(*pfunThread)(void *pvParam); //* 執行緒入口函數
void *pvParam; //* 傳遞給入口函數的使用者引數
} STCB_PSTACKTHREAD, *PSTCB_PSTACKTHREAD;
這個結構體與目標os高度相關,其用於儲存協定棧內部工作執行緒列表。協定棧內部設計了一個one-shot定時器。該定時器被用於一些需要等待一小段時間才能進行後續處理或定期執行的業務模組。這個定時器是以執行緒的方式實現的。協定棧的核心業務邏輯均與這個one-shot定時器執行緒有關。協定棧被目標系統載入時該執行緒將由os_thread_onpstack_start()函數自動啟動。這個函數要啟動的執行緒列表就被儲存在STCB_PSTACKTHREAD結構體陣列中。這個陣列是一個靜態儲存時期的變數,變數名為lr_stcbaPStackThread,在os_adapter.c中定義。STCB_PSTACKTHREAD結構體需要定義哪些成員變數由目標os提供的執行緒啟動函數的入口引數決定。我們會將執行緒啟動用到的入口引數值定義在lr_stcbaPStackThread陣列中,然後由os_thread_onpstack_start()將這些引數值傳遞給執行緒啟動函數啟動相應工作執行緒。
另外一個地方是臨界區保護函數:
#define os_critical_init() //* 臨界區初始化
#define os_enter_critical() //* 進入臨界區(關中斷)
#define os_exit_critical() //* 退出臨界區(開中斷)
一般的os臨界區保護函數基本都是進入臨界區關中斷,離開臨界區開中斷。程式碼非常簡單,所以這裡直接給出了三個函數宏原型,移植時請依據目標系統具體情形新增對應的開、關中斷程式碼即可。
這個檔案的核心工作就是編碼實現 os_adapter.h 檔案宣告的所有與 os 相關的介面函數。os_adapter.h中有這些函數的詳細功能說明,移植時按照說明實現具體功能即可,不再贅述。
//* 當前執行緒休眠指定的秒數,引數 unSecs 指定要休眠的秒數
OS_ADAPTER_EXT void os_sleep_secs(UINT unSecs);
//* 當前執行緒休眠指定的毫秒數,單位:毫秒
OS_ADAPTER_EXT void os_sleep_ms(UINT unMSecs);
//* 獲取系統啟動以來已執行的秒數(從 0 開始)
OS_ADAPTER_EXT UINT os_get_system_secs(void);
//* 執行緒同步鎖初始化,成功返回同步鎖控制程式碼,失敗則返回INVALID_HMUTEX
OS_ADAPTER_EXT HMUTEX os_thread_mutex_init(void);
//* 執行緒同步區加鎖
OS_ADAPTER_EXT void os_thread_mutex_lock(HMUTEX hMutex);
//* 執行緒同步區解鎖
OS_ADAPTER_EXT void os_thread_mutex_unlock(HMUTEX hMutex);
//* 刪除執行緒同步鎖,釋放該資源
OS_ADAPTER_EXT void os_thread_mutex_uninit(HMUTEX hMutex);
//* 號誌初始化,引數unInitVal指定初始號誌值, unCount指定號誌最大數值
OS_ADAPTER_EXT HSEM os_thread_sem_init(UINT unInitVal, UINT unCount);
//* 投遞號誌
OS_ADAPTER_EXT void os_thread_sem_post(HSEM hSem);
//* 等待號誌到達,引數unWaitSecs指定要等待的超時時間(單位為秒):
//* 0,一直等下去直至號誌到達,收到訊號則返回值為0,出錯則返回值為-1;
//* 大於0,等待指定時間,如果指定時間內號誌到達,則返回值為0,超時則返回值為1,出錯則返回值為-1
OS_ADAPTER_EXT INT os_thread_sem_pend(HSEM hSem, INT nWaitSecs);
//* 號誌去初始化,釋放該資源
OS_ADAPTER_EXT void os_thread_sem_uninit(HSEM hSem);
//* 啟動協定棧內部工作執行緒
OS_ADAPTER_EXT void os_thread_onpstack_start(void *pvParam);
#if SUPPORT_PPP
//* 開啟 tty 裝置,返回 tty 裝置控制程式碼,引數 pszTTYName 指定要開啟的 tty 裝置的名稱
OS_ADAPTER_EXT HTTY os_open_tty(const CHAR *pszTTYName);
//* 關閉 tty 裝置,引數 hTTY 為要關閉的 tty 裝置的控制程式碼
OS_ADAPTER_EXT void os_close_tty(HTTY hTTY);
//* 向 hTTY 指定的 tty 裝置傳送資料,返回實際傳送的資料長度
//* hTTY:裝置控制程式碼
//* pubData:指標,指向要傳送的資料的指標
//* nDataLen:要傳送的資料長度
OS_ADAPTER_EXT INT os_tty_send(HTTY hTTY, UCHAR *pubData, INT nDataLen);
//* 從引數 hTTY 指定的 tty 裝置等待接收資料,阻塞型
//* hTTY:裝置控制程式碼
//* pubRcvBuf:指標,指向資料接收緩衝區的指標,用於儲存收到的資料
//* nRcvBufLen:接收緩衝區的長度
//* nWaitSecs:等待的時長,單位:秒。0 一直等待;直至收到資料或報錯,大於 0,等待指定秒數;小於 0,不支援
OS_ADAPTER_EXT INT os_tty_recv(HTTY hTTY, UCHAR *pubRcvBuf, INT nRcvBufLen, INT nWaitSecs);
//* 復位 tty 裝置,這個函數名稱體現了 4g 模組作為 tty 裝置的特殊性,其功能從本質上看就是一個 modem,modem 裝置出現通訊
//* 故障時,最好的修復故障的方式就是直接復位,復位可以修復絕大部分的因軟體問題產生的故障
OS_ADAPTER_EXT void os_modem_reset(HTTY hTTY);
#endif