系统时间变化导致sem_timedwait函数执行异常的一种解决方法
1. 问题简介
4G或5G通信模块通常是以AP+CP方式组合,AP侧运行linux系统,CP侧运行RTOS系统。
一般的Linux系统系统时间可以从硬件RTC获取。RTC可以由主板电池供电,独立于系统运行。当系统关机,断电时依然维护时间。但对于有些嵌入式linxu系统,没有使用硬件RTC,系统时间通常需要linux系统完全跑起来后通过网络方式或其他方式获取。
对于4G或5G通信模块,通常CP侧从网络中获取时间,AP侧再从CP侧同步时间。同步时间的时候,linux系统时间会发生跳变,这会导致某些使用绝对时间做判断的应用存在出现异常的可能。
2. 使用sem_timedwait函数带来的问题
当前很多模组厂商在sdx24/sdx55平台都使用了自研的oem-app应用程序,该程序在模块启动的时候就开始运行,并且在启动运行的过程中存在从CP同步系统时间的操作。oem-app程序多个地方使用sem_timedwait函数进行信号量超时等待,如果在调用sem_timedwait的时候发生了系统时间变化,会导致sem_timedwait提前超时退出,产生非预期的结果,通常会导致程序运行逻辑出现异常。
举oem-app中一个使用sem_timedwait函数的例子。
oem-app中wds模块负责拨号,使用了一个信号量wds_al_init_semaphore,在wds模块完成初始化后post该信号量,以此通知其他模块wds模块已完成初始化。处理AT+GTRNDIS拨号的线程会调用sem_timedwait(&wds_al_init_semaphore, &timeOut);函数等待wds模块完成初始化。如果在这个等待过程中发送系统时间变化,sem_timedwait会提前退出,从而wds模块还没有完成初始化的时候就进行拨号操作,这会导致拨号看起来成功了,实际业务还不通。因此需要尽量避免使用sem_timedwait进行超时等待操作。
3. 问题解决
使用pthread_cond_wait函数可以解决系统时间变化提前超时退出的问题。pthread_cond_timedwait函数可以配置成按相对时间等待超时。
需要配置条件变量的属性,具体如下:
pthread_condattr_init(&oem_sem->sem_cond_attr);
pthread_condattr_setclock(&oem_sem->sem_cond_attr, CLOCK_MONOTONIC);
pthread_cond_init(&oem_sem->sem_cond, &oem_sem->sem_cond_attr);
3.1 自定义sem类型:oem_cmn_sem_t
针对此问题,增加自定义sem类型oem_cmn_sem_t,提供信号量操作的函数,使用相对时间等待。
typedef struct oem_cmn_sem_struct {
pthread_mutex_t sem_mutex;
pthread_cond_t sem_cond;
pthread_condattr_t sem_cond_attr;
int sem_cond_value;
} oem_cmn_sem_t;
提供如下函数进行信号量操作:
extern int oem_cmn_sem_init(oem_cmn_sem_t * oem_sem);
extern int oem_cmn_sem_post(oem_cmn_sem_t * oem_sem);
extern int oem_cmn_sem_timedwait(oem_cmn_sem_t * oem_sem, const oem_clk_Time_t * wait_time);
extern int oem_cmn_sem_reset_value(oem_cmn_sem_t * oem_sem);
3.2 oem_cmn_sem_t操作函数实现
int oem_cmn_sem_init(oem_cmn_sem_t * oem_sem)
{
if (!oem_sem) return ERR_OEM_SEM_FAIL;
pthread_mutex_init(&oem_sem->sem_mutex, NULL);
pthread_condattr_init(&oem_sem->sem_cond_attr);
pthread_condattr_setclock(&oem_sem->sem_cond_attr, CLOCK_MONOTONIC);
pthread_cond_init(&oem_sem->sem_cond, &oem_sem->sem_cond_attr);
oem_sem->sem_cond_value = 0;
return ERR_OEM_SEM_OK;
}
int oem_cmn_sem_post(oem_cmn_sem_t * oem_sem)
{
if (!oem_sem) return ERR_OEM_SEM_FAIL;
pthread_mutex_lock(&oem_sem->sem_mutex);
oem_sem->sem_cond_value++;
pthread_cond_signal(&oem_sem->sem_cond);
pthread_mutex_unlock(&oem_sem->sem_mutex);
}
int oem_cmn_sem_timedwait(oem_cmn_sem_t * oem_sem, const oem_clk_Time_t * wait_time)
{
int result = ERR_OEM_SEM_OK;
if (!oem_sem) return ERR_OEM_SEM_FAIL;
pthread_mutex_lock(&oem_sem->sem_mutex);
if (oem_sem->sem_cond_value <= 0)
{
if ((NULL == wait_time)
|| (wait_time->sec == 0 && wait_time->usec == 0))
{
pthread_cond_wait(&oem_sem->sem_cond, &oem_sem->sem_mutex);
}
else
{
struct timespec timeOut;
clock_gettime(CLOCK_MONOTONIC, &timeOut);
timeOut.tv_sec += wait_time->sec;
timeOut.tv_nsec += (wait_time->usec * 1000);
if (timeOut.tv_nsec >= 1000000000)
{
timeOut.tv_nsec -= 1000000000;
timeOut.tv_sec += 1;
}
result = pthread_cond_timedwait(&oem_sem->sem_cond, &oem_sem->sem_mutex, &timeOut);
if ( result == ETIMEDOUT )
{
OEM_LOG_INFO(OEM_OEM_LOG_MEDIAM, "Timed out waiting wds al thread init!\n");
result = ERR_OEM_SEM_TIMEOUT;
}
else
{
result = ERR_OEM_SEM_OK;
}
}
}
pthread_mutex_unlock(&oem_sem->sem_mutex);
return result;
}
int oem_cmn_sem_reset_value(oem_cmn_sem_t * oem_sem)
{
if (!oem_sem) return ERR_OEM_SEM_FAIL;
pthread_mutex_lock(&oem_sem->sem_mutex);
oem_sem->sem_cond_value = 0;
pthread_mutex_unlock(&oem_sem->sem_mutex);
return ERR_OEM_SEM_OK;
}
4. 参考文档
https://blog.csdn.net/yichigo/article/details/23459613