当前位置: 首页 > Linux

修改系统时间,导致sem_timedwait函数一直阻塞解决分析

时间:2023-04-07 01:53:35 Linux

修改系统时间,导致sem_timedwait一直阻塞问题解决分析引入最近修复的项目问题,发现系统运行时时间往前修改,会导致sem_timedwait函数一直阻塞。查找后发现intsem_timedwait(sem_t*sem,conststructtimespec*abs_timeout);传入的第二个阻塞时间参数是绝对时间戳,所以这个函数是有缺陷的。sem_timedwait的缺陷原因:假设当前系统时间为1565000000(2019-08-0518:13:20),sem_timedwait传入的阻塞等待时间戳为1565000100(2019-08-0518:15:00),然后sem_timedwait需要阻塞1分40秒(100秒)。如果在sem_timedwait的阻塞过程中,将系统时间修改为1500000000(2017-07-1410:40:00),那么sem_timedwait此时会阻塞2年以上!这就是sem_timedwait的缺陷!!sem_timedwait函数介绍intsem_timedwait(sem_t*sem,conststructtimespec*abs_timeout);如果信号量大于0,信号量减1,立即恢复正常如果信号量小于0,阻塞等待,阻塞超时返回失败(errno设置为ETIMEDOUT)第二个参数abs_timeout,指向一个指定绝对超时时间的结构。结果由自纪元1970-01-0100:00:00+0000(UTC)以来的秒数和纳秒数组成。这个结构定义如下structtimespec{time_ttv_sec;/*秒*/longtv_nsec;/*纳秒*/};解决办法可以通过类似sem_timedwait函数的sem_trywait+usleep来实现,不会因为系统时间往前改而一直出现阻塞的问题。sem_trywait函数介绍函数sem_trywait()和sem_wait()有点不同,即如果信号量的当前值为0,则返回错误,而不是阻塞调用。错误值errno设置为EAGAIN。sem_trywait()实际上是sem_wait()的非阻塞版本。intsem_trywait(sem_t*sem)执行成功返回0,执行失败返回-1,信号量的值保持不变。sem_trywait+usleep的方法实现了主要思想:无论信号量是否为0,sem_trywait函数都会立即返回。当函数正常返回时,不会有usleep;当函数异常返回时,会通过usleep实现延时。具体实现是下面代码中的boolWait(size_ttimeout)函数:#include#include#include#includesem_tg_sem;//从系统中获取startupInlineuint64_tGetTimeConvSeconds(timespec*curTime,uint32_tfactor){//CLOCK_MONOTONIC:从系统启动的那一刻开始计时,不受用户更改系统时间的影响clock_gettime(CLOCK_MONOTONIC,curTime);returnstatic_cast(curTime->tv_sec)*factor;}//获取自系统启动以来单调递增的时间——转换单位为微秒uint64_tGetMonnotonicTime(){timespeccurTime;uint64_t结果=GetTimeConvSeconds(&curTime,1000000);结果+=static_cast(curTime.tv_nsec)/1000;returnresult;}//通过sem_trywait+usleep实现//如果信号量大于0,减少信号量,立即返回true//如果信号量小于0,则阻塞等待,阻塞超时返回falseWait(size_t超时){constsize_ttimeoutUs=超时*1000;//延迟时间从毫米转换为微秒constsize_tmaxTimeWait=10000;//休眠时间最长为10000微秒,即10毫秒size_ttimeWait=1;//休眠时间,默认1微秒size_tdelayUs=0;//延迟休眠的剩余时间constuint64_tstartUs=GetMonnotonicTime();//循环前的开始时间,以微秒为单位uint64_telapsedUs=0;//过期时间,以微秒为单位intret=0;do{//如果信号量大于0,减少信号量,立即返回trueif(sem_trywait(&g_sem)==0){returntrue;}//系统信号会立即返回falseif(errno!=EAGAIN){returnfalse;}//delayUs必须大于等于0,因为do-while的条件是elapsedUs<=timeoutUs.delayUs=timeoutUs-elapsedUs;//取最小休眠时间timeWait=std::min(delayUs,timeWait);//睡眠单位是微秒ret=usleep(timeWait);如果(ret!=0){返回false;}//睡眠延迟时间加倍timeWait*=2;//休眠延迟时间不能超过最大值timeWait=std::min(timeWait,maxTimeWait);//计算开始时间到达elapsedUs=GetMonnotonicTime()-startUs;}尽管(经过的时间<=超时时间);//如果当前循环时间超过预设的延迟时间,退出循环//如果超时退出,返回falsereturnfalse;}//获取需要延迟等待时间的绝对时间戳inlinetimespec*GetAbsTime(size_t毫秒,timespec&absTime){//CLOCK_REALTIME:系统实时时间,随系统实时时间变化,即从UTC1970-1-10:0:0开始计时,//如果系统时间由中间时刻的用户如果是其他的,那么相应的时间也会随之改变。clock_gettime(CLOCK_REALTIME,&absTime);absTime.tv_sec+=毫秒/1000;absTime.tv_nsec+=(毫秒%1000)*1000000;//纳秒进位秒if(absTime.tv_nsec>=1000000000){absTime.tv_sec+=1;absTime.tv_nsec-=1000000000;}return&absTime;}//sem_timedwait实现的sleep--有缺陷//如果信号量大于0,则减少信号量,立即返回true//如果信号量小于0,则阻塞等待,并且块超时时返回false,&absTime)!=0){returnfalse;}returntrue;}intmain(void){boolsignaled=false;uint64_tstartUs=0;uint64_telapsedUs=0;//初始化信号量,编号为0sem_init(&g_sem,0,0);///////////////////////Sem_trywait+USLEEP的睡眠///////////////////////////////////////////////////////////////////////////////贡斯////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////我等待(1000);//获取超时等待时间,单位微秒elapsedUs=GetMonnotonicTime()-startUs;//输出信号:0等待时间:1000msstd::cout<<"signaled:"<