POSIX定时器以两倍于预期频率运行

3
为了创建一个高精度计时器,我编写了一个模块,使用timer_create()函数实例化了一个POSIX计时器。它使用CLOCK_REALTIME作为其时钟类型,SIGEV_SIGNAL作为通知方法,SIGRTMIN作为信号编号。它的信号处理程序什么也不做,只有一个sem_post()。计时器使用timer_settime()启动,定时器间隔可以是任意毫秒数。
该模块的用户可以等待计时器触发;等待功能基本上是通过sem_wait()实现的。我的单线程测试应用程序创建计时器并以所需的i毫秒间隔启动它。然后,它循环执行,等待计时器触发x次。它使用gettimeofday()来计时所有这些操作。
期望总循环时间为x*i毫秒。但实际上,它只需要恰好0.5*x*i毫秒。我尝试了几种xi的组合,测试的总执行时间从几秒钟到几十秒不等。结果一致,计时器的运行频率是预期/期望频率的两倍。
这是在运行 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/Linuxgcc 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)

2
你考虑过使用 timerfd_create http://man7.org/linux/man-pages/man2/timerfd_create.2.html 和 poll http://man7.org/linux/man-pages/man2/poll.2.html 吗?而且你真的应该展示一小段代码... 你有用过 strace 来跟踪你的应用程序吗? - Basile Starynkevitch
@BasileStarynkevitch:没有,我还没有考虑使用timerfd_create,会研究一下的。我也还没有做过strace。根据您的建议,我发布了代码,并上传了一个可重现问题的软件包这里 - Reinier Torenbeek
@BasileStarynkevitch:strace揭示了问题的原因。请看下面我的回答。感谢您的帮助! - Reinier Torenbeek
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,它揭示了问题的原因。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接