c++posixvxworks

Posix Timer timer is not accurate


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 :

enter image description here


Solution

  • 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.