3

为了创建一个高精度计时器,我编写了一个模块,该模块使用该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


我已经上传了代码的精简版本,其中包括编译代码的脚本和重现问题的测试。

定时器类本身的代码如下:

/* 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)
4

1 回答 1

2

这个问题的根本原因是它sem_wait()被唤醒了两次:一次是因为被信号中断了,一次是因为信号量被释放,它确实需要唤醒sem_post()。检查返回值sem_wait()errno = EINTR解决问题:

/**
 * 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;
}

感谢 Basile Starynkevitch 建议使用strace,这揭示了问题的原因。

于 2013-03-14T19:37:22.140 回答