I'm working on vxWorks 7. I've developed a class to implement POSIX timer. I'm working on a Real Time Process.
The timer is designed to run in single-shot or periodic mode.
The problem is I'm getting random timeout. I've attached a list of observed timeout against expected timeut.
Worst problem is that the same code perfectly in Linux (a non-deterministic General Purpose OS) compared to deterministic RTOS.
Source Code :
posix_timer.hpp
#pragma once
#include <map>
#include <ctime>
#include <string>
#include <utility>
#include <csignal>
#include <cstdint>
#include <functional>
#include <chrono>
typedef int signal_t;
static const std::int64_t getCurrentTimeInMicroseconds()
{
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}
class PosixTimer
{
public:
static bool initialize(void);
static inline const int signal_rt_min = SIGRTMIN, signal_rt_max = SIGRTMAX;
using callback_timer_t = std::function<void(std::uint64_t)>;
PosixTimer(callback_timer_t callback, std::uint64_t callback_data, bool periodic = false) :
PosixTimer(periodic)
{
this->callback = callback;
this->callback_data = callback_data;
}
PosixTimer(bool periodic);
PosixTimer();
PosixTimer(const PosixTimer& instance) = delete; //Delete copy constructor
PosixTimer& operator=(const PosixTimer& instance) = delete; //Delete Copy Assignment
PosixTimer(PosixTimer&& source);
PosixTimer& operator=(PosixTimer&& source);
~PosixTimer();
bool create(void);//Create timer only
bool create(std::uint64_t timeout, bool periodic);//Initialize the Periodic Timer Object, Create timer, set timeout and start timer
// bool create(std::uint64_t timeout);//Create timer, set timeout and start timer
bool update(std::uint64_t timeout);//Stop timer, update set timeout and start timer
bool start(std::uint64_t timeout);//Set timeout and start timer
bool start(void);//Starts the timer with the timeout value (refer private timeout variables)
bool stop(void);//Stops the timer
bool destroy(void);//Destroys the timer. Can't be reused without calling create
void registerCallback(callback_timer_t callback, std::uint64_t callback_data);
static std::uint64_t in_nanoseconds(std::uint64_t timeout);
static std::uint64_t in_microseconds(std::uint64_t timeout);
static std::uint64_t in_milliseconds(std::uint32_t timeout);
static std::uint64_t in_seconds(std::uint16_t timeout);
private:
static inline std::map<signal_t, bool> signal_usage_status;
signal_t signal_id;
static inline std::uint8_t total_timer_count = 0;
std::uint8_t timer_count = 0;
bool periodic;
std::uint64_t timeout;// in nano-seconds
timer_t timer_id;
sigset_t mask;
std::uint64_t callback_data;
struct sigevent sev;
struct sigaction sa;
struct itimerspec its;
bool timer_valid {false};
static void handler(int sig, siginfo_t *si, void *uc);
callback_timer_t callback;
bool block_signal(void);
bool unblock_signal(void);
};
class SignalNumberExhausted : public std::exception
{
private:
static inline const std::string message = "Real-time signal number exhausted";
public:
SignalNumberExhausted() {}
const char* what() const noexcept override
{
return message.c_str();
}
};
posix_timer.cpp
#include "posix_timer.hpp"
#include <iostream>
#include <cstdio>
// The range of supported real-time signals is defined by the macros SIGRTMIN and SIGRTMAX.
// Programs should never refer to real-time signals using hard-coded numbers, but instead should always refer to
// real-time signals using the notation SIGRTMIN+n, and include suitable (run-time) checks that SIGRTMIN+n
// does not exceed SIGRTMAX
// the top priority RT signal is SIGRTMIN; the least (lowest) priority RT signal is SIGRTMAX.
// If different real-time signals are sent to a process, they are delivered starting with the lowest-numbered signal.
//(i.e., low-numbered signals have highest priority.)
// #define SIG SIGRTMIN
#define CLOCK_ID CLOCK_REALTIME
bool PosixTimer::initialize(void)
{
try
{
for(int i = PosixTimer::signal_rt_min; i <= PosixTimer::signal_rt_max; i++)
{
// std::cout << i << std::endl;
PosixTimer::signal_usage_status[i] = false;
}
return true;
}
catch (const std::exception &e)
{
// Log Error
return false;
}
}
PosixTimer::PosixTimer()
{
std::cout << "Inside constructor PosixTimer::PosixTimer()\n";
}
PosixTimer::PosixTimer(bool periodic)
{
// std::cout << "Inside constructor PosixTimer::PosixTimer(bool periodic)\n";
this->periodic = periodic;
this->timer_count = SIGRTMIN + PosixTimer::total_timer_count++;
// NativePosixTimer::total_timer_count++;
bool signal_assigned = false;
for (auto& [key, value]: PosixTimer::signal_usage_status)
{
if(value == false)//Timer not assigned
{
this->signal_id = key; //Assign the free signal
value = true;
signal_assigned = true;
break;
}
}
if(signal_assigned == false)
{
// std::cout << "In PosixTimer-> If\n";
throw SignalNumberExhausted();
}
}
PosixTimer::PosixTimer(PosixTimer &&source)
{
// std::cout << "Inside Move Constructor PosixTimer::PosixTimer(PosixTimer &&source)\n";
this->timer_id = source.timer_id;
// this->timer_valid = source.timer_valid;
this->signal_id = source.signal_id;
this->timer_count = source.timer_count;
this->periodic = source.periodic;
this->timeout = source.timeout;// in nano-seconds
this->mask = source.mask;
// this->callback_data = source.callback_data;
std::exchange(this->callback_data, source.callback_data);
this->sev = source.sev;
this->sa = source.sa;
this->its = source.its;
this->callback = source.callback;
// this->si
//Reset source timer_id to 0 and timer validity (timer_valid) to false
source.timer_id = 0;
source.timer_valid = false;
if(this->callback)
{
std::cout << "this->callback not empty in move constructor\n";
}
else
{
std::cout << "this->callback empty in move constructor\n";
}
}
PosixTimer &PosixTimer::operator=(PosixTimer &&source)
{
// std::cout << "Inside Move Assignment PosixTimer &PosixTimer::operator=(PosixTimer &&source)\n";
if (this != &source) //performs no operation if you try to assign the object to itself
{
this->timer_id = source.timer_id;
// this->timer_valid = source.timer_valid;
this->signal_id = source.signal_id;
this->timer_count = source.timer_count;
this->periodic = source.periodic;
this->timeout = source.timeout;// in nano-seconds
this->mask = source.mask;
// this->callback_data = source.callback_data;
std::exchange(this->callback_data, source.callback_data);
this->sev = source.sev;
this->sa = source.sa;
this->its = source.its;
this->callback = source.callback;
//Reset source timer_id to 0 and timer validity (timer_valid) to false
source.timer_id = 0;
source.timer_valid = false;
if(this->callback)
{
std::cout << "this->callback not empty in move assignment\n";
}
else
{
std::cout << "this->callback empty in move assignment\n";
}
this->sev.sigev_value.sival_ptr = this;
}
return *this;
}
PosixTimer::~PosixTimer()
{
if(this->timer_valid == true)
{
if(this->destroy() == false)
{
//Log Error
}
}
}
bool PosixTimer::create(void)
{
int returnValue;
this->timer_count = SIGRTMIN + PosixTimer::total_timer_count++;
bool signal_assigned = false;
for (auto& [key, value]: PosixTimer::signal_usage_status)
{
if(value == false)//Timer not assigned
{
this->signal_id = key; //Assign the free signal
value = true;
signal_assigned = true;
break;
}
}
if(signal_assigned == false)
{
// std::cout << "In Create-> If\n";
throw SignalNumberExhausted();
}
//End of Addition by IK on 06-04-2024
// Establish handler for timer signal.
this->sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
sigemptyset(&this->sa.sa_mask);
if (sigaction(this->signal_id, &this->sa, nullptr) == -1)
{
// Log Error
perror("sigaction in PosixCreate ");
return false;
}
// Block timer signal temporarily.
if (this->block_signal() == false)
{
return false;
}
// Create a timer
this->sev.sigev_notify = SIGEV_SIGNAL; // Upon timer expiration, generate the signal sigev_signo for the process.
this->sev.sigev_signo = this->signal_id;
this->sev.sigev_value.sival_ptr = this;
returnValue = timer_create(CLOCK_ID, &this->sev, &this->timer_id);
if (returnValue == -1)
{
// Log Error
perror("timer_create");
return false;
}
this->timer_valid = true;
return true;
}
bool PosixTimer::create(std::uint64_t timeout, bool periodic)
{
this->periodic = periodic;
bool return_status = this->create();
if (return_status == false)
{
return false;
}
return this->start(timeout);
}
bool PosixTimer::update(std::uint64_t timeout)
{
// //std::cerr<<"TIMER UPDATE CALLED, Timer ID:"<<this->timer_id<<std::endl;
bool return_status = this->stop();
if (return_status == false)
{
return false;
}
return this->start(timeout);
}
bool PosixTimer::start(std::uint64_t timeout)
{
this->its.it_value.tv_sec = timeout / 1000000000;
this->its.it_value.tv_nsec = timeout % 1000000000;
if (periodic == true)
{
this->its.it_interval.tv_sec = this->its.it_value.tv_sec;
this->its.it_interval.tv_nsec = this->its.it_value.tv_nsec;
}
else
{
this->its.it_interval.tv_sec = 0;
this->its.it_interval.tv_nsec = 0;
}
return this->start();
}
bool PosixTimer::start(void)
{
int return_status = timer_settime(this->timer_id, 0, &this->its, nullptr);
if (return_status == -1)
{
perror("PosixTimer::start->timer_settime");
return false;
}
this->unblock_signal();
return true;
}
bool PosixTimer::stop(void)
{
this->block_signal();
struct itimerspec its;
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 0;
int return_status = timer_settime(this->timer_id, 0, &its, nullptr);
if (return_status == -1)
{
perror("PosixTimer::stop->timer_settime");
return false;
}
return true;
}
bool PosixTimer::destroy(void)
{
int return_status = timer_delete(this->timer_id);
if (return_status == -1)
{
return false;
}
else
{
this->timer_valid = false;
PosixTimer::signal_usage_status.at(this->signal_id) = false;
return true;
}
}
void PosixTimer::registerCallback(callback_timer_t callback, std::uint64_t callback_data)
{
this->callback_data = callback_data;
this->callback = callback;
}
void PosixTimer::handler(int sig, siginfo_t *si, void *uc)
{
try
{
PosixTimer *timer = reinterpret_cast<PosixTimer *>(si->si_value.sival_ptr);
if(timer->callback)
{
std::cout << "timer->callback is not empty\n";
}
else
{
std::cout << "timer->callback is empty\n";
}
std::cerr<<"Current time in microseconds from posix timer class "<< getCurrentTimeInMicroseconds()<<std::endl;
timer->callback(timer->callback_data);
}
catch(const std::exception& e)
{
std::cerr << "PosixTimer::handler exception. " << e.what() << std::endl;
}
}
bool PosixTimer::block_signal(void)
{
// Block timer signal temporarily.
#ifdef NDEBUG
printf("Blocking signal %d\n", this->signal_id);
#endif
// Empty a signal set
if (sigemptyset(&this->mask) == -1)
{
perror("sigemptyset");
return false;
}
// Add a signal to a signal set
// std::cout << "Signal ID : " << this->signal_id << std::endl;
if (sigaddset(&this->mask, this->signal_id) == -1)
{
perror("sigaddset");
return false;
}
// Change the signal mask of the calling thread
if (sigprocmask(SIG_BLOCK, &this->mask, nullptr) == -1)
{
perror("sigprocmask");
return false;
}
return true;
}
bool PosixTimer::unblock_signal(void)
{
// std::printf("Unblocking signal %d\n", this->signal_id);
if (sigprocmask(SIG_UNBLOCK, &this->mask, NULL) == -1)
{
perror("timer start- sigprocmask");
return false;
}
return true;
}
std::uint64_t PosixTimer::in_milliseconds(std::uint32_t timeout)
{
return timeout * 1000000ul;
}
std::uint64_t PosixTimer::in_nanoseconds(std::uint64_t timeout)
{
return timeout;
}
std::uint64_t PosixTimer::in_microseconds(std::uint64_t timeout)
{
return timeout * 1000ul;
}
std::uint64_t PosixTimer::in_seconds(std::uint16_t timeout)
{
return timeout * 1000000000ul;
}
main.cpp
#include "posix_timer.hpp"
#include <iostream>
#include <vector>
#include <any>
#include <unistd.h>
#include <chrono>
void handler(std::uint64_t data)
{
try
{
std::cout<<"Current time: "<<getCurrentTimeInMicroseconds();
}
catch(const std::exception& e)
{
std::cerr << "Main::handler : " << e.what() << '\n';
}
}
int main(int argc, char const *argv[])
{
PosixTimer::initialize();
PosixTimer t;
t.registerCallback(handler, 10);
bool return_status = t.create(PosixTimer::in_milliseconds(1), true);
if(return_status == true)
{
std::cerr << "Timer Started\n";
}
else
{
std::cerr << "Timer Failed to Start\n";
}
while(1)
{
}
return 0;
}
Time Recorded :
From the documentation of VXWorks, the clock resolution is hardware-dependant, and in "many cases is 1/60th of a second".
The values that you show in your table are in increments of 1/60th of a second, matching this resolution in documentation. The values aren't random, but rather match what could be expected.