Таймер Posix Timer неточен

Я работаю над vxWorks 7. Я разработал класс для реализации таймера POSIX. Я работаю над процессом в реальном времени.
Таймер предназначен для работы в однократном или периодическом режиме.
Проблема в том, что я получаю случайный тайм-аут. Я приложил список наблюдаемых таймаутов в сравнении с ожидаемыми таймаутами.
Худшая проблема заключается в том, что в Linux (недетерминированная ОС общего назначения) совершенно один и тот же код по сравнению с детерминированной RTOS.

Исходный код :

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;
}

Время записи:

Почему ты путаешь <ctime> с <chrono>? Если std::chrono доступен, вам следует использовать chrono::high_resolution_clock и high_resolution_clock::duration в качестве значения тайм-аута. Целые числа нежелательны при наличии определенных типов данных с хорошей абстракцией. Этот код не кажется минималистичным; слишком долго для таймера. Вы не используете список инициализаторов конструктора. Ваш конструктор перемещения и оператор присваивания перемещают почти каждый элемент, который является определением по умолчанию и не требует кодирования; если хотя бы один член пропущен, вы обречены. Отдельный класс может выполнять ход по умолчанию.

Red.Wave 18.06.2024 15:27

Всего несколько замечаний: поведение вашего кода неопределенное: while(1){}. Тогда код не является минимально воспроизводимым примером и выходные данные отсутствуют. Наконец, как только все заработает, рассмотрите возможность отправки своего кода на codereview.stackexchange.com.

Ulrich Eckhardt 18.06.2024 17:08
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
63
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Судя по документации VXWorks, разрешение часов зависит от оборудования и «во многих случаях составляет 1/60 секунды».

Значения, показанные в таблице, указаны с шагом 1/60 секунды, что соответствует разрешению, указанному в документации. Значения не случайны, а скорее соответствуют ожидаемым.

Если я использую счетчик отметок времени Intel (TSC) — это 64-битный регистр, доступный на процессорах Intel, начиная с Pentium. Столкнусь ли я с той же проблемой? Доступны ли их средства вычислений с координацией времени Intel, которые могут помочь мне решить эту проблему? Я использую процессор Tiger Lake 12-го поколения.

Dark Sorrow 19.06.2024 07:33

@DarkSorrow Должно быть доступно более точное управление временем, просто документально подтверждено, что выбранный вами конкретный интерфейс (интервальные таймеры POSIX), вероятно, имеет грубые временные интервалы в VxWorks (в тактах, которые обычно составляют 1/60 или 1/100 секунды). ). В имеющейся у меня копии документации VxWorks (в разделе timerLib) упоминается использование nanosleep для интервалов менее секунды (хотя это приостановка, а не обратный вызов).

Thomas Jager 19.06.2024 13:33

Можете ли вы настроить тактовую частоту этой сборки VxWorks? Тики действительно являются основной временной разверткой в ​​ОСРВ, все «происходит» с шагом в такты. Еще следует рассмотреть вопрос о том, действительно ли этот дизайн приложения является правильным решением в данной ситуации. Я не уверен, какое программное обеспечение вы пишете, но, как упоминалось в комментариях к вопросу, это много шаблонного кода, просто чтобы обернуть таймер. Какие требования заставляют вас идти по этому пути, но при этом вам нужен очень точный интервальный контроль?

Thomas Jager 19.06.2024 13:49

Другие вопросы по теме