我创建了一个计时器类,它以用户提供的间隔执行用户提供的操作(不带参数的函数没有返回类型)。这个动作应该在自己的线程中进行 - 即。创建计时器时,会创建一个新线程,该线程由一个循环组成,该循环在执行回调之前使用sigwait
等待信号进入。我想要使用的信号将是从SIGRTMIN
到SIGRTMAX
的任何地方。我希望能够创建多个计时器对象,这意味着多个线程和多个信号(每个计时器一个线程和信号)。使用this post,timer_create
man page和pthread_sigmask
man page作为参考,这就是我所拥有的:
//timer.h
#ifndef TIMERS_H
#define TIMERS_H
#include <signal.h>
#include <time.h>
#include <inttypes.h>
#include <stdio.h>
#include <pthread.h>
class CTimer
{
public:
CTimer(uint64_t period_ms, void(*callback)(void), int sig );
private:
typedef void (*Callback)(void);
Callback m_pCallback;
timer_t timerID;
struct sigevent sev;
struct itimerspec its;
struct sigaction sa;
uint8_t timerNum;
pthread_t thread_id;
sigset_t set;
int signal_ID;
void* loop();
friend void* run_loop(void* arg);
};
#endif // TIMERS_H
和
//timer.cpp
#include "timers.h"
void* run_loop(void* arg)
{
return static_cast<CTimer*>(arg)->loop();
}
CTimer::CTimer(uint64_t period_ms, void(*callback)(void), int sig):
m_pCallback(callback), signal_ID(sig)
{
//create mask to send appropriate signal to thread
int s;
sigemptyset(&set);
s = sigaddset(&set, signal_ID);
if (s != 0)
{
printf("error on sigaddset\n");
}
s = pthread_sigmask(SIG_BLOCK, &set, NULL);
if (s != 0)
{
printf("error on pthread_sigmask\n");
}
//create new thread that will run the signal handler
s = pthread_create(&thread_id, NULL, run_loop, this);
if (s != 0)
{
printf("error on pthread_create\n");
}
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = signal_ID;
sev.sigev_value.sival_ptr = &timerID;
if (timer_create(CLOCK_REALTIME, &sev, &timerID) == -1)
{
printf("error on timer create\n");
}
its.it_value.tv_sec = period_ms / 1000;
its.it_value.tv_nsec = period_ms % 1000;
its.it_interval.tv_sec = its.it_value.tv_sec;
its.it_interval.tv_nsec = its.it_value.tv_nsec;
if (timer_settime(timerID, 0, &its, NULL) == -1)
{
printf("error on timer settime\n");
}
}
void* CTimer::loop()
{
int s = 0;
while (1)
{
s = sigwait(&set, &signal_ID);
m_pCallback();
}
}
为了测试我正在使用它:
//driver.cpp
#include <stdio.h>
#include <unistd.h>
#include "sys/time.h"
#include "timers.h"
uint64_t get_time_usec()
{
static struct timeval _time_stamp;
gettimeofday(&_time_stamp, NULL);
return _time_stamp.tv_sec*1000000 + _time_stamp.tv_usec;
}
void callbacktest1()
{
printf("tick1 %" PRIu64 " \n", get_time_usec());
}
void callbacktest2()
{
printf("tick2 %" PRIu64 " \n", get_time_usec());
}
int main(int argv, char *argc[])
{
CTimer t1(1000, callbacktest1, SIGRTMIN);
CTimer t2(2000, callbacktest2, SIGRTMIN+1);
pause();
}
运行时,它会很快崩溃并出现错误&#34;实时信号1&#34;。如果我在gdb中运行它,我会得到
Starting program: /home/overlord/MySource/Timer/driver
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff75ee700 (LWP 21455)]
[New Thread 0x7ffff6ded700 (LWP 21456)]
tick1 1477336403700925
tick1 1477336404700920
Program received signal SIG35, Real-time event 35.
[Switching to Thread 0x7ffff75ee700 (LWP 21455)]
0x00007ffff7bcc0c1 in do_sigwait (sig=0x7fffffffdbc8, set=<optimized out>) at ../nptl/sysdeps/unix/sysv/linux/../../../../../sysdeps/unix/sysv/linux/sigwait.c:60
60 ../nptl/sysdeps/unix/sysv/linux/../../../../../sysdeps/unix/sysv/linux/sigwait.c: No such file or directory.
这很有趣,因为35是SIGRTMIN + 1等于的。那么也许我没有正确路由信号?如果我只在driver.cpp文件中创建一次计时器实例,那么事情似乎正常。任何想法都表示赞赏。
我也很好奇这是否是我正在尝试做的正确方法。在我做的一些简短的测试中,使用系统信号看起来比使用睡眠和睡眠更加稳定,以消耗未使用的循环时间。
答案 0 :(得分:1)
我的猜测是用于唤醒第二个线程(CTimer t2
)的信号没有被第一个线程(CTimer t1
)阻止。线程中的信号掩码是从父线程继承的,因此当您启动第一个线程时,它只会阻塞SIGRTMIN
信号,但SIGRTMIN+1
仍然可以传递给它。对实时信号的标准反应是终止过程,这就是发生的事情。您可以通过阻止CTimer类启动的所有线程中的所有实时信号来测试此理论。
我不确定为什么你认为sleep / usleep不如你自己的解决方案可靠,使用正确的模式与usleep(基本上期望它可以更快地返回并在循环中等待)总是对我有效。< / p>
答案 1 :(得分:1)
我不确定为什么你认为睡眠/睡眠不太可靠 你自己的解决方案,使用正确的模式与usleep(基本上 期待它能够更快地返回并且在循环中等待) 为我工作好。
我使用以下代码进行了基本测试:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <inttypes.h>
#include <math.h>
#define CLOCKID CLOCK_REALTIME
#define SIG SIGRTMIN
#define SQ(x) ((x)*(x))
#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
} while (0)
uint64_t start_time_us = 0;
double error_ms = 0;
int64_t count = 0;
int64_t last_time_us = 0;
uint64_t period_ns;
uint64_t get_time_usec()
{
static struct timeval _time_stamp;
gettimeofday(&_time_stamp, NULL);
return _time_stamp.tv_sec*1000000 + _time_stamp.tv_usec;
}
static void
handler(int sig, siginfo_t *si, void *uc)
{
uint64_t timestamp_us = get_time_usec();
double dt_ms = (double)(timestamp_us - last_time_us)/1000;
double elapsed_ms = (double)(timestamp_us - start_time_us)/1000;
error_ms += SQ((dt_ms - (double)period_ns/1000000.0));
count++;
last_time_us = timestamp_us;
}
namespace hidi
{
void pause(const double& tSeconds)
{
unsigned int decimal = static_cast<unsigned int>(floor(tSeconds));
double fraction = tSeconds-static_cast<double>(decimal);
if (decimal > 0)
sleep(decimal);
usleep(static_cast<unsigned long>(floor(fraction*1000000.0)));
return;
}
}
int
main(int argc, char *argv[])
{
timer_t timerid;
struct sigevent sev;
struct itimerspec its;
//sigset_t mask;
struct sigaction sa;
if (argc != 3) {
fprintf(stderr, "Usage: %s <test length-secs> <period-millisec>\n",
argv[0]);
exit(EXIT_FAILURE);
}
uint64_t period_ms = atoll(argv[2]);
period_ns = period_ms * 1000000;
/// FIRST TEST LOOP RATE STABILITY USING THE TIMER
// THE TIMER WILL USE SIGRTMIN (DEFINED ABOVE) AND WILL MEASURE
// STATISTICS ON LOOP STABILITY
/* Establish handler for timer signal */
printf("Establishing handler for signal %d\n", SIG);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIG, &sa, NULL) == -1)
errExit("sigaction");
/* Create the timer */
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIG;
sev.sigev_value.sival_ptr = &timerid;
if (timer_create(CLOCKID, &sev, &timerid) == -1)
errExit("timer_create");
printf("timer ID is 0x%lx\n", (long) timerid);
/* Start the timer */
printf("Timing period is %zu ms\n", period_ms);
its.it_value.tv_sec = period_ns / 1000000000;
its.it_value.tv_nsec = period_ns % 1000000000;
its.it_interval.tv_sec = its.it_value.tv_sec;
its.it_interval.tv_nsec = its.it_value.tv_nsec;
if (timer_settime(timerid, 0, &its, NULL) == -1)
errExit("timer_settime");
start_time_us = last_time_us = get_time_usec();
printf("Sleeping for %d seconds\n", atoi(argv[1]));
while ((get_time_usec()-start_time_us)/1000000 < atoi(argv[1]))
{
sleep(1); //this just prevents the while loop from spinning out of control
// the sleep function is interrupted with the signal callback is
// executed. All the magic happens in the callback.
}
printf("ave error: %8.6f ms %zu samples\n",sqrt((double)error_ms/(double)count), count);
timer_delete(timerid); // disarm / delete timer
/// START TEST USING SLEEP / USLEEP
start_time_us = last_time_us = get_time_usec();
error_ms = count = 0;
while ((get_time_usec()-start_time_us)/1000000 < atoi(argv[1]))
{
uint64_t timestamp_us = get_time_usec();
double dt_ms = (double)(timestamp_us - last_time_us)/1000;
double elapsed_ms = (double)(timestamp_us - start_time_us)/1000;
error_ms += SQ((dt_ms - (double)period_ns/1000000.0));
//printf("et=%8.6f ms, dt=%8.6f ms ave error %f\n", elapsed_ms, dt_ms, error_ms/count);
count++;
last_time_us = timestamp_us;
uint64_t consumed_time_us = get_time_usec()-timestamp_us;
uint64_t remaining_looptime_us = (period_ns/1000) - consumed_time_us;
hidi::pause((double)remaining_looptime_us/1000000.0);
}
printf("ave error: %8.6f ms %zu samples\n",sqrt((double)error_ms/(double)count), count);
exit(EXIT_SUCCESS);
}
测试时间从10毫秒到2秒,使用计时器似乎更稳定。似乎使用sleep / usleep方法,误差与周期成正比,即10 ms周期为2-3 ms,而1000 ms周期为300 - 400 ms。使用计时器,错误在不同的时期内非常稳定。