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
在这里仅单独给出os_thread_onpstack_start()函数的伪代码实现:
void os_thread_onpstack_start(void *pvParam)
{
//* 建立工作线程
INT i;
for (i = 0; i < sizeof(lr_stcbaPStackThread) / sizeof(STCB_PSTACKTHREAD); i++)
{
//* 在这里添加目标系统提供的线程启动函数,启动工作线程
……
}
}