Как получить наиболее точные периодические прерывания в реальном времени в Linux? - PullRequest
7 голосов
/ 29 апреля 2011

Я хочу, чтобы меня прерывали на частотах, кратных десяти, поэтому включение прерываний из / dev / rtc не идеально. Я хотел бы поспать 1 миллисекунду или 250 микросекунд между прерываниями.

Включение периодических прерываний из / dev / hpet работает довольно хорошо, но на некоторых машинах это не работает. Очевидно, что я не могу использовать его на машинах, которые на самом деле не имеют HPET. Но я не могу заставить его работать на некоторых машинах, у которых hpet также доступен как источник синхронизации. Например, в Core 2 Quad пример программы, включенной в документацию по ядру, завершается с ошибкой в ​​HPET_IE_ON при установке на опрос.

Было бы лучше использовать интерфейс itimer, предоставляемый Linux, вместо непосредственного взаимодействия с драйвером аппаратного устройства. А в некоторых системах itimer обеспечивает периодические прерывания, которые более стабильны во времени. То есть, поскольку hpet не может прерывать с той частотой, которую я хочу, прерывания начинают дрейфовать со времени стены. Но я вижу, что некоторые системы спят намного дольше (10+ миллисекунд), чем они должны использовать itimer.

Вот тестовая программа, использующая itimer для прерываний. В некоторых системах он выводит только одно предупреждение, что он спал около 100 микросекунд или около того в течение целевого времени. На других, он будет распечатывать пакеты с предупреждением, что он спал 10+ миллисекунд в течение целевого времени. Скомпилируйте с -lrt и запустите с sudo chrt -f 50 [имя]

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <error.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <signal.h>
#include <fcntl.h>
#define NS_PER_SECOND 1000000000LL
#define TIMESPEC_TO_NS( aTime ) ( ( NS_PER_SECOND * ( ( long long int ) aTime.tv_sec ) ) \
    + aTime.tv_nsec )

int main()
{
    // Block alarm signal, will be waited on explicitly
    sigset_t lAlarm;
    sigemptyset( &lAlarm );
    sigaddset( &lAlarm, SIGALRM  );
    sigprocmask( SIG_BLOCK, &lAlarm, NULL );

    // Set up periodic interrupt timer
    struct itimerval lTimer;
    int lReceivedSignal = 0;

    lTimer.it_value.tv_sec = 0;
    lTimer.it_value.tv_usec = 250;
    lTimer.it_interval = lTimer.it_value;

    // Start timer
    if ( setitimer( ITIMER_REAL, &lTimer, NULL ) != 0 )
    {
        error( EXIT_FAILURE, errno, "Could not start interval timer" );
    }
    struct timespec lLastTime;
    struct timespec lCurrentTime;
    clock_gettime( CLOCK_REALTIME, &lLastTime );
    while ( 1 )
    {
        //Periodic wait
        if ( sigwait( &lAlarm, &lReceivedSignal ) != 0 )
        {
            error( EXIT_FAILURE, errno, "Failed to wait for next clock tick" );
        }
        clock_gettime( CLOCK_REALTIME, &lCurrentTime );
        long long int lDifference = 
            ( TIMESPEC_TO_NS( lCurrentTime ) - TIMESPEC_TO_NS( lLastTime ) );
        if ( lDifference  > 300000 )
        {
            fprintf( stderr, "Waited too long: %lld\n", lDifference  );
        }
        lLastTime = lCurrentTime;
    }
    return 0;
}

Ответы [ 2 ]

7 голосов
/ 14 апреля 2013

У меня была такая же проблема с установкой setitimer ().Проблема в том, что ваш процесс запланирован политикой SCHED_OTHER со статическим уровнем приоритета 0 по умолчанию.Это означает, что вы находитесь в пуле со всеми другими процессами, и динамические приоритеты решают.В момент некоторой загрузки системы вы получаете задержки.

Решение состоит в том, чтобы использовать системный вызов sched_setscheduler (), увеличить статический приоритет как минимум до одного и указать политику SCHED_FIFO.Это приводит к значительному улучшению.

#include <sched.h>
...
int main(int argc, char *argv[])
{
    ...
    struct sched_param schedp;
    schedp.sched_priority = 1;
    sched_setscheduler(0, SCHED_FIFO, &schedp);
    ...
}

Вы должны работать от имени пользователя root, чтобы сделать это.Альтернатива - использовать программу chrt, чтобы сделать то же самое, но вы должны знать PID вашего RT-процесса.

sudo chrt -f -p 1 <pid>

См. Мой пост в блоге об этом здесь .

5 голосов
/ 29 апреля 2011

Независимо от используемого вами механизма синхронизации, он сводится к комбинации изменений в состоянии выполнения вашей задачи, когда вызывается планировщик ядра (обычно 100 или 1000 раз в секунду), и к конфликту процессора с другими процессами.

Механизм, который, как я обнаружил, обеспечивает «лучшее» время в Linux (в том числе и в Windows), заключается в следующем:

  1. Поместите процесс на экранированный процессор
  2. Сначала процесс должен спать в течение 1 мс.Если на экранированном процессоре ваш процесс должен проснуться прямо на границе тиков планировщика ОС
  3. Используйте RDTSC напрямую или CLOCK_MONOTONIC для захвата текущего времени.Используйте это как нулевое время для расчета абсолютного времени пробуждения для всех будущих периодов.Это поможет минимизировать дрейф с течением времени.Его нельзя полностью устранить, поскольку аппаратное хронометраж изменяется со временем (проблемы с температурой и т. П.), Но это довольно хорошее начало.
  4. Создайте функцию сна, которая спит на 1 мс меньше целевого абсолютного времени пробуждения (так как этосамый точный планировщик ОС может быть) затем сжать процессор в узком цикле, постоянно проверяя значение RDTSC / CLOCK_REALTIME.

Требуется некоторая работа, но вы можете получить довольно хорошие результаты, используя этот подходСоответствующий вопрос, на который вы, возможно, захотите взглянуть, можно найти здесь .

...