为了创建一个高精度的计时器,我编写了一个使用timer_create()
函数实例化POSIX计时器的模块。它使用CLOCK_REALTIME
作为时钟类型,SIGEV_SIGNAL
作为通知方法,SIGRTMIN
作为信号编号。它的信号处理程序只做sem_post()
。定时器使用timer_settime()
启动,定时器间隔为任意数毫秒。
模块的用户可以等待计时器滴答;等待功能基本上由sem_wait()
实现。我的单线程测试应用程序创建计时器并以所需的i
毫秒间隔启动它。然后循环,等待x
次计时器触发。它使用gettimeofday()
来计算所有这些。
期望循环的总时间为x
* i
毫秒。相反,它只需完全 0.5 * x
* i
毫秒。我尝试了x
和i
的几种组合,测试的总执行时间从几秒到几十秒不等。结果一致是计时器以预期/期望频率的两倍运行。
此问题在CentOS 5.5 Linux 2.6.18-194.el5 #1 SMP Fri Apr 2 14:58:14 EDT 2010 x86_64 x86_64 x86_64 GNU/Linux
gcc 4.1.2
我上传了a stripped down version of the code,其中包含编译代码的脚本和重现问题的测试。
计时器类本身的代码如下:
/* PosixTimer: simple class for high-accuracy timer functionality */
/* Interface */
#include "PosixTimer.h"
/* Implementation */
#include <pthread.h>
#include <time.h>
#include <signal.h>
#include <semaphore.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define TIMER_SIGNAL SIGRTMIN
#define ALLOCATE_AND_CLEAR(pVar) \
pVar = malloc(sizeof(*pVar)); \
memset(pVar, 0, sizeof(*pVar))
#define FREE_AND_NULL(pVar) \
free(pVar); \
pVar = NULL
struct PosixTimerImpl {
timer_t timerId;
struct itimerspec timeOut;
sem_t semaphore;
};
static void
PosixTimer_sigHandler(
int sig,
siginfo_t *info,
void *ptr)
{
PosixTimer *self = (PosixTimer *)(info->si_value.sival_ptr);
if (NULL != self) {
sem_post(&self->semaphore);
}
}
static void
PosixTimer_setTimeoutValue(
PosixTimer *self,
unsigned int msecInterval)
{
if (NULL != self) {
self->timeOut.it_value.tv_sec = msecInterval / 1000;
self->timeOut.it_value.tv_nsec = (msecInterval % 1000) * 1000000;
self->timeOut.it_interval.tv_sec = msecInterval / 1000;
self->timeOut.it_interval.tv_nsec = (msecInterval % 1000) * 1000000;
}
}
/* Public methods */
/**
* Constructor for the PosixTimer class. Ticks happen every <interval> and are not queued
*/
PosixTimer *
PosixTimer_new(
unsigned int msecInterval)
{
PosixTimer *self = NULL;
int clockId = CLOCK_REALTIME;
struct sigevent evp;
int status;
/* Construction */
ALLOCATE_AND_CLEAR(self);
/* Initialization */
PosixTimer_setTimeoutValue(self, msecInterval);
evp.sigev_signo = TIMER_SIGNAL;
evp.sigev_notify = SIGEV_SIGNAL;
evp.sigev_value.sival_ptr = self;
status = timer_create(clockId, &evp, &self->timerId);
if (0 == status) {
sem_init(&self->semaphore, 0, 0);
} else {
printf("Error creating timer, retVal = %d\n", status);
FREE_AND_NULL(self);
}
return self;
}
/**
* Destructor
*/
void
PosixTimer_delete(
PosixTimer *self)
{
int status;
sem_post(&self->semaphore);
status = sem_destroy(&self->semaphore);
if (0 != status) {
printf("sem_destroy failed\n");
}
status = timer_delete(self->timerId);
if (0 != status) {
printf("timer_delete failed\n");
}
FREE_AND_NULL(self);
}
/**
* Kick off timer
*/
void
PosixTimer_start(
PosixTimer *self)
{
#define FLAG_RELATIVE 0
int status;
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, TIMER_SIGNAL);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = PosixTimer_sigHandler;
status = sigaction(TIMER_SIGNAL, &sa, NULL);
if (0 != status) {
printf("sigaction failed\n");
} else {
status = timer_settime(self->timerId, FLAG_RELATIVE,
&self->timeOut, NULL);
if (0 != status) {
printf("timer_settime failed\n");
}
}
}
/**
* Wait for next timer tick
*/
void
PosixTimer_wait(
PosixTimer *self)
{
/* Just wait for the semaphore */
sem_wait(&self->semaphore);
}
用于显示问题的测试:
/* Simple test app to test PosixTimer */
#include "PosixTimer.h"
#include <sys/time.h>
#include <stdio.h>
int main(
int argc,
const char ** argv)
{
#define USEC_PER_MSEC (1000)
#define NSEC_PER_MSEC (1000000)
#define MSEC_PER_SEC (1000)
PosixTimer *timer1 = NULL;
struct timeval before, after;
double dElapsedMsecs;
int elapsedMsecs;
int iCount1;
printf("Running PosixTimer tests\n");
#define DURATION_MSEC (10000)
#define INTERVAL_MSEC_TEST1 (5)
#define ACCURACY_MSEC_TEST1 (100)
timer1 = PosixTimer_new(INTERVAL_MSEC_TEST1);
iCount1 = DURATION_MSEC/INTERVAL_MSEC_TEST1;
printf("Running test: %d milliseconds in %d cycles\n", DURATION_MSEC, iCount1);
gettimeofday(&before, NULL);
PosixTimer_start(timer1);
while (0 < iCount1) {
PosixTimer_wait(timer1);
//printf(".");
iCount1--;
}
gettimeofday(&after, NULL);
//printf("\n");
dElapsedMsecs = (after.tv_sec - before.tv_sec) * MSEC_PER_SEC;
dElapsedMsecs += (after.tv_usec - before.tv_usec) / USEC_PER_MSEC;
elapsedMsecs = dElapsedMsecs+0.5;
if ((ACCURACY_MSEC_TEST1 > (elapsedMsecs - DURATION_MSEC)) &&
(ACCURACY_MSEC_TEST1 > (DURATION_MSEC - elapsedMsecs))) {
printf("success");
} else {
printf("failure");
}
printf(" (expected result in range (%d -- %d), got %d)\n",
DURATION_MSEC - ACCURACY_MSEC_TEST1,
DURATION_MSEC + ACCURACY_MSEC_TEST1,
elapsedMsecs);
return 0;
}
结果是
-bash-3.2$ ./DesignBasedTest
Running PosixTimer tests
Running test: 10000 milliseconds in 2000 cycles
failure (expected result in range (9900 -- 10100), got 5000)
答案 0 :(得分:2)
这个问题的根本原因是sem_wait()
被唤醒了两次:一次是因为它被信号打断了,一次是因为由于信号量被{{1}释放而真的需要唤醒}。检查sem_post()
和sem_wait()
的返回值是否解决了问题:
errno = EINTR
感谢Basile Starynkevitch建议使用/**
* Wait for next timer tick
*/
int
PosixTimer_wait(
PosixTimer *self)
{
int result;
/* Just wait for the semaphore */
do {
result = (0 == sem_wait(&self->semaphore));
if (!result) {
result = errno;
}
} while (EINTR == result);
return result;
}
,这揭示了问题的原因。