onps棧移植說明(2)——編譯器及os適配層移植

2. 位元組對齊及基礎數據類型定義

       協議棧源碼(碼雲/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位元組整型數。

3. OS適配層

       對於 os 適配層,主要的移植工作就幾塊:1)提供多任務(線程)建立函數;2)提供系統級的秒級、毫秒級延時函數及運行時長統計函數;3)提供同步(互斥)鎖相關操作函數;4)提供信號量操作函數;5)提供一組臨界區保護也就是中斷禁止/使能函數。os 適配層的移植工作涉及 os_datatype.h、os_adapter.h、os_adapter.c 三個文件。

3.1 os_datatype.h

       這個文件負責完成與目標操作系統相關的數據類型定義,主要就是互斥鎖、信號量、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文件中的冗餘定義直接注釋掉即可;如不存在,則直接使用樣例文件中的通用定義即可。

3.2 os_adapter.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臨界區保護函數基本都是進入臨界區關中斷,離開臨界區開中斷。代碼非常簡單,所以這裡直接給出了三個函數宏原型,移植時請依據目標系統具體情形添加對應的開、關中斷代碼即可。

3.3 os_adapter.c

       這個文件的核心工作就是編碼實現 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