POSIX计时器以预期频率的两倍运行 [英] POSIX timer runs at twice the expected frequency

查看:88
本文介绍了POSIX计时器以预期频率的两倍运行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为了创建高精度计时器,我编写了一个模块,该模块使用 sem_post() 之外什么也没做.计时器使用 timer_settime() 启动,可以使用任意数量的毫秒为计时器间隔.

In order to create a high accuracy timer, I have written a module that instantiates a POSIX timer using the timer_create() function. It uses CLOCK_REALTIME as its clock kind, SIGEV_SIGNAL as notification method and SIGRTMIN as the signal number. Its signal handler does nothing but a sem_post(). The timer is started using timer_settime(), with any number of milliseconds as the timer interval.

该模块的用户可以等待定时器滴答;等待功能实质上是由 sem_wait() 实现的.我的单线程测试应用程序创建了计时器,并以所需的i毫秒间隔启动了计时器.然后循环,等待x次以触发计时器.它使用 gettimeofday() 来计时所有时间.

The user of the module can wait for a timer-tick; the wait functionality is essentially implemented by a sem_wait(). My single-threaded test application creates the timer and starts it with the desired interval of i milliseconds. Then it loops, waiting x times for the timer to trigger. It uses gettimeofday() to time all this.

期望循环的总时间为x * i毫秒.相反,它只需要精确地 0.5 * x * i毫秒.我尝试了xi的几种组合,测试的总执行时间从几秒到几十秒不等.结果始终是计时器以预期/期望频率的两倍运行.

The expectation is that the total time for the loop would be x*i milliseconds. In stead, it only takes exactly 0.5*x*i milliseconds. I have tried several combinations of x and i, with the total execution time of the test ranging from a few seconds to tens of seconds. The result is consistently that the timer runs at twice the expected/desired frequency.

此代码在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

我已经上传了精简版的代码,其中包括用于编译代码的脚本并进行重现该问题的测试.

I have uploaded a stripped down version of the code which includes a script to compile the code and a test to reproduce the issue.

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

The code of the timer class itself is as follows:

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

推荐答案

此问题的根本原因是sem_wait()被唤醒了两次:一次是因为它被信号打断了,一次是因为它确实需要唤醒由于sem_post()释放了信号量.检查sem_wait()errno = EINTR的返回值可以解决此问题:

The root cause of this problem was that sem_wait() was woken up twice: once because it was interrupted by the signal, and once because it really needed to wake up due to the semaphore being released by sem_post(). Checking for the return value of sem_wait() and errno = EINTR resolved the issue:

/**
 * 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,这揭示了问题的原因.

Thanks to Basile Starynkevitch for suggesting the use of strace, which revealed the cause of the problem.

这篇关于POSIX计时器以预期频率的两倍运行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆