Linux, точность по таймеру - PullRequest
6 голосов
/ 26 июня 2010

У меня есть система, которая нуждается в точности не менее 10 мсек для таймеров.
Я выбрал timerfd, так как он мне подходит, но обнаружил, что даже до 15 миллисекунд он не совсем точен, или я не понимаю, как это работает.

Время, измеренное мной, составляло до 21 мсек с таймером 10 мсек.
Я собрал быстрый тест, который показывает мою проблему.
Вот тест:

#include <sys/timerfd.h>
#include <time.h>
#include <string.h>
#include <stdint.h>

int main(int argc, char *argv[]){

    int timerfd = timerfd_create(CLOCK_MONOTONIC,0);
    int milliseconds = atoi(argv[1]);
    struct itimerspec timspec;
    bzero(&timspec, sizeof(timspec));
    timspec.it_interval.tv_sec = 0;
    timspec.it_interval.tv_nsec = milliseconds * 1000000;
    timspec.it_value.tv_sec = 0;
    timspec.it_value.tv_nsec = 1;

    int res = timerfd_settime(timerfd, 0, &timspec, 0);
    if(res < 0){
       perror("timerfd_settime:");
    }
    uint64_t expirations = 0;
    int iterations = 0;
    while( res = read(timerfd, &expirations, sizeof(expirations))){
        if(res < 0){ perror("read:"); continue; }
        if(expirations > 1){
            printf("%lld expirations, %d iterations\n", expirations, iterations);
            break;
        }
        iterations++;
    }
}

И выполнено так:

Zack ~$ for i in 2 4 8 10 15; do echo "intervals of $i milliseconds"; ./test $i;done
intervals of 2 milliseconds
2 expirations, 1 iterations
intervals of 4 milliseconds
2 expirations, 6381 iterations
intervals of 8 milliseconds
2 expirations, 21764 iterations
intervals of 10 milliseconds
2 expirations, 1089 iterations
intervals of 15 milliseconds
2 expirations, 3085 iterations

Даже при некоторых возможных задержках задержка в 15 миллисекунд звучит для меня слишком много.

Ответы [ 4 ]

12 голосов
/ 27 июня 2010

Попробуйте изменить его следующим образом, это должно в значительной степени гарантировать, что он никогда не пропустит пробуждение, но будьте осторожны с ним, поскольку выполнение приоритета в реальном времени может сильно блокировать вашу машину, если она не спит, также вам может потребоваться установить все так, чтобы у вашего пользователя была возможность запускать вещи с приоритетом в реальном времени (см. /etc/security/limits.conf)

#include <sys/timerfd.h>
#include <time.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <sched.h>

int main(int argc, char *argv[]) 
{
    int timerfd = timerfd_create(CLOCK_MONOTONIC,0);
    int milliseconds = atoi(argv[1]);
    struct itimerspec timspec;
    struct sched_param schedparm;

    memset(&schedparm, 0, sizeof(schedparm));
    schedparm.sched_priority = 1; // lowest rt priority
    sched_setscheduler(0, SCHED_FIFO, &schedparm);

    bzero(&timspec, sizeof(timspec));
    timspec.it_interval.tv_sec = 0;
    timspec.it_interval.tv_nsec = milliseconds * 1000000;
    timspec.it_value.tv_sec = 0;
    timspec.it_value.tv_nsec = 1;

    int res = timerfd_settime(timerfd, 0, &timspec, 0);
    if(res < 0){
       perror("timerfd_settime:");
    }
    uint64_t expirations = 0;
    int iterations = 0;
    while( res = read(timerfd, &expirations, sizeof(expirations))){
        if(res < 0){ perror("read:"); continue; }
        if(expirations > 1){
            printf("%ld expirations, %d iterations\n", expirations, iterations);
            break;
        }
        iterations++;
    }
}

Если вы используете потоки, вы должны использовать pthread_setschedparam вместо sched_setscheduler.

Realtime также не о низкой задержке, а о гарантиях, RT означает, что если вы хотите просыпаться ровно раз в секунду на секунду, вы БУДЕТЕ, нормальное планирование не дает вам этого, возможно, вы решите разбудить Вы поднялись на 100 мс позже, потому что в любом случае у него была другая работа. Если вы хотите просыпаться каждые 10 мс, и вам ДЕЙСТВИТЕЛЬНО это нужно, тогда вы должны настроить себя на выполнение задачи в реальном времени, тогда ядро ​​будет разбудить вас каждые 10 мсек в обязательном порядке. Если задача реального времени с более высоким приоритетом не занята работой.

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

цитата из http://www.ganssle.com/articles/realtime.htm

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

Обратите внимание, что это определение не дает предположения о частоте или период выполнения заданий. Микросекунда или неделю - если пропущен срок вызывает сбой, то задача имеет жесткие требования в реальном времени.

Мягкое реальное время в значительной степени то же самое, за исключением того, что пропущенный крайний срок, хотя и нежелательный, не является концом света (например, воспроизведение видео и аудио - это мягкие задачи в реальном времени, вы не хотите пропустить отображение кадра, или не хватает буфера, но если вы это сделаете, это всего лишь мгновенный икота, и вы просто продолжите). Если то, что вы пытаетесь сделать, это «мягкое» реальное время, я бы не стал работать с приоритетом в реальном времени, так как вы обычно должны вовремя получать пробуждения (или, по крайней мере, близко к нему).

EDIT:

Если вы не работаете в режиме реального времени, ядро ​​по умолчанию выдаст любые таймеры, которые вы сделаете несколько «провисшими», чтобы он мог объединить ваш запрос, чтобы проснуться с другими событиями, которые происходят в моменты, близкие к тому, который вы запрашивали (что если другое событие находится в пределах вашего «расслабленного» времени, оно не разбудит вас в тот момент, когда вы попросили, но немного раньше или позже, в то же время оно уже собиралось сделать что-то еще, это экономит энергию).

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

3 голосов
/ 27 июня 2010

У меня такое ощущение, что ваш тест сильно зависит от аппаратного обеспечения. Когда я запустил вашу примерную программу в моей системе, она зависла на 1 мс. Чтобы сделать мой тест значимым для моего компьютера, мне пришлось перейти от миллисекунд к микросекундам. (Я изменил множитель с 1_000_000 на 1_000.)

$ grep 1000 test.c
    timspec.it_interval.tv_nsec = microseconds * 1000;
$ for i in 1 2 4 5 7 8 9 15 16 17\
 31 32 33 47 48 49 63 64 65 ; do\
 echo "intervals of $i microseconds";\
 ./test $i;done
intervals of 1 microseconds
11 expirations, 0 iterations
intervals of 2 microseconds
5 expirations, 0 iterations
intervals of 4 microseconds
3 expirations, 0 iterations
intervals of 5 microseconds
2 expirations, 0 iterations
intervals of 7 microseconds
2 expirations, 0 iterations
intervals of 8 microseconds
2 expirations, 0 iterations
intervals of 9 microseconds
2 expirations, 0 iterations
intervals of 15 microseconds
2 expirations, 7788 iterations
intervals of 16 microseconds
4 expirations, 1646767 iterations
intervals of 17 microseconds
2 expirations, 597 iterations
intervals of 31 microseconds
2 expirations, 370969 iterations
intervals of 32 microseconds
2 expirations, 163167 iterations
intervals of 33 microseconds
2 expirations, 3267 iterations
intervals of 47 microseconds
2 expirations, 1913584 iterations
intervals of 48 microseconds
2 expirations, 31 iterations
intervals of 49 microseconds
2 expirations, 17852 iterations
intervals of 63 microseconds
2 expirations, 24 iterations
intervals of 64 microseconds
2 expirations, 2888 iterations
intervals of 65 microseconds
2 expirations, 37668 iterations

(Довольно интересно, что я получил самые длинные пробежки от 16 и 47 микросекунд, но 17 и 48 были ужасны.)

time (7) предлагает несколько советов о том, почему наши платформы такие разные:

   High-Resolution Timers
       Before Linux 2.6.21, the accuracy of timer and sleep system
       calls (see below) was also limited by the size of the jiffy.

       Since Linux 2.6.21, Linux supports high-resolution timers
       (HRTs), optionally configurable via CONFIG_HIGH_RES_TIMERS.  On
       a system that supports HRTs, the accuracy of sleep and timer
       system calls is no longer constrained by the jiffy, but instead
       can be as accurate as the hardware allows (microsecond accuracy
       is typical of modern hardware).  You can determine whether
       high-resolution timers are supported by checking the resolution
       returned by a call to clock_getres(2) or looking at the
       "resolution" entries in /proc/timer_list.

       HRTs are not supported on all hardware architectures.  (Support
       is provided on x86, arm, and powerpc, among others.)

Все строки 'resolution' в моем / proc / timer_list равны 1 нс в моей (предположительно смехотворно мощной) системе x86_64.

Я решил попытаться выяснить, где находится «точка разрыва» на моем компьютере, но разочаровался в 110 микросекундах:

$ for i in 70 80 90 100 110 120 130\
 ; do echo "intervals of $i microseconds";\
 ./test $i;done
intervals of 70 microseconds
2 expirations, 639236 iterations
intervals of 80 microseconds
2 expirations, 150304 iterations
intervals of 90 microseconds
4 expirations, 3368248 iterations
intervals of 100 microseconds
4 expirations, 1964857 iterations
intervals of 110 microseconds
^C

90 микросекунд проходили три миллиона итераций, прежде чем несколько раз выходили из строя; это в 22 раза лучшее разрешение, чем в вашем первом тесте, поэтому я бы сказал, что при правильном оборудовании 10 мс не должны быть сложными. (90 микросекунд - это в 111 раз лучшее разрешение, чем 10 миллисекунд.)

Но если ваше оборудование не предоставляет таймеры для таймеров с высоким разрешением, то Linux не сможет вам помочь, не прибегая к SCHED_RR или SCHED_FIFO. И даже тогда, возможно, другое ядро ​​могло бы лучше предоставить вам необходимую поддержку программного таймера.

Удачи. :)

1 голос
/ 26 июня 2010

Вот теория.Если для вашей системы Гц установлено значение 250 (как обычно), то у вас есть разрешение таймера 4 миллисекунды.Как только ваш процесс будет заменен планировщиком, вполне вероятно, что ряд других процессов будет запланирован и запущен до того, как ваш процесс получит еще один временной интервал.Это может объяснить, что вы видите разрешение таймера в диапазоне от 15 до 21 миллисекунды.Единственный способ обойти это - запустить ядро ​​в реальном времени.

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

0 голосов
/ 26 июня 2010

В зависимости от того, что еще делает система, процесс возврата к вашей задаче может быть немного медленным. Если у вас нет «реальной» системы реального времени, нет гарантии, что она будет работать лучше, чем вы видите, хотя я согласен, что результат немного разочаровывает.

Вы можете (в основном) исключить время переключения / планировщика задач. Если у вас есть запас ресурсов процессора (и электроэнергии!), Жестоким, но эффективным решением было бы занятое вращение цикла ожидания.

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

Однажды я написал такую ​​систему под Windows XP, чтобы вращать шаговый двигатель, подавая равномерно распределенные импульсы до 40K раз в секунду, и она работала нормально. Конечно, ваш пробег может отличаться.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...