LINUX.ORG.RU

Отправка UDP-пакетов по таймеру

 , ,


0

2

Всем доброго времени суток.

Есть ли в Linux механизм, который позволил бы из userspace отдать ядру UDP-пакет и указать момент времени, в который он должен быть отправлен?

Время, когда нужно послать пакет, известно точно, но время, когда есть возможность его послать — плавает, из-за случайной задержки (~5мс) между моментом истечения таймера в ядре и моментом передачи управления потоку в userspace, который вызовет sendto().

Интересуют способы уменьшения этого джиттера. Настойщий real-time не требуется.

Есть ли в userspace варианты кроме nice, SCHED_RR, etc?


Можно попробовать использовать RAW-sockets, но тогда придётся самому формировать заголовки фреймов. И не факт, что получится. А отложенной отправки буфера - я о таком не слышал/не читал. Тут, разве что, самому в ядро лезть и реализовывать.

nickleiten ★★★
()
Ответ на: комментарий от nickleiten

Можно попробовать использовать RAW-sockets, но тогда придётся самому формировать заголовки фреймов.

Гм, а чем тут помогут RAW-sockets?

Тут, разве что, самому в ядро лезть и реализовывать.

Ну да, но этого делать не хочется.

gv
() автор топика

время, когда есть возможность его послать — плавает, из-за случайной задержки (~5мс)

ну так убирайте задержку, рефакторинг вам в зубы

anonymous
()
Ответ на: комментарий от anonymous

ну так убирайте задержку, рефакторинг вам в зубы

Так это задержка на переключение контекста, выполнение других процессов, etc.

Что-то вроде:

nanosleep();
// <-- random delay here
sendto();

Предлагаете сделать как-то по-другому?

gv
() автор топика
Ответ на: комментарий от gv

Так это задержка на переключение контекста

Переключение контекста длится десятки микросекунд.

выполнение других процессов, etc.

Решается выставлением планировщика реального времени и нужного приоритета.

tailgunner ★★★★★
()
Ответ на: комментарий от gv

невозможно советовать не понимания архитектуры,

а по вашему псевдокоду, достаточно убрать random delay here, ну так уберите их и задержка пропадет

anonymous
()
Ответ на: комментарий от tailgunner

Переключение контекста длится десятки микросекунд.

А, ясно.

Решается выставлением планировщика реального времени и нужного приоритета.

Ну это понятно.

gv
() автор топика
Ответ на: комментарий от anonymous

по вашему псевдокоду, достаточно убрать random delay here, ну так уберите их и задержка пропадет

Так

// <-- random delay here

-- это комментарий, кода там нет :)

gv
() автор топика
Ответ на: комментарий от gv

все зависит от сложности приложение, если не детская поделка то делайте отдельный асинхронный треид, в котором в пуле ожидания будете держать пакеты на отправку и ихнее время, а банальным select выгребаем и отправляем

или все тоже самое, только всю архитектуру перевести на асинхронное io

или вместо nanosleep поэкспериментировать с select/poll/итд

anonymous
()
Ответ на: комментарий от anonymous

отдельный асинхронный треид, в котором в пуле ожидания будете держать пакеты на отправку и ихнее время

Так и есть.

или вместо nanosleep поэкспериментировать с select/poll/итд

А чем это поможет? Учитывая, что отправлять пакеты нужно не при первой возможности, а при наступлении нужного момента времени.

или все тоже самое, только всю архитектуру перевести на асинхронное io

Асинхронное io — это aio(7)? Тоже не понимаю, чем он здесь поможет, тем более что у меня итак поток.

gv
() автор топика
Ответ на: комментарий от gv

Решается выставлением планировщика реального времени и нужного приоритета.

Ну это понятно.

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

tailgunner ★★★★★
()
Ответ на: комментарий от gv

Так и есть.

видимо не так,

while (1) { min,buf = queue.poplast(); nanosleep(min); send(buf); }

так что ли? где queue очередь уже от

anonymous
()
Ответ на: комментарий от tailgunner

это ж насколько должна быть система прогружена?

случайной задержки (~5мс)

что то мне кажется сильно надуманым

anonymous
()
Ответ на: комментарий от tailgunner

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

Гипотетический механизм, озвученный в топике, чем-то плох концептуально или просто не был никем реализован?

(функция вроде sendto_at(buffer, address, time))

gv
() автор топика
Ответ на: комментарий от Deleted

У вас там именно nanosleep? Не пробовали clock_nanosleep?

Не пробовал, спасибо, попробую.

gv
() автор топика
Ответ на: комментарий от anonymous

while (1) { min,buf = queue.poplast(); nanosleep(min); send(buf); }

так что ли? где queue очередь уже от

Да.

gv
() автор топика
Ответ на: комментарий от Elyas

Подозреваю, что плавающая задержка у ТС где-то до вызова nanosleep, а не после.

Я пришел к выводу, что именно после. Записываю clock_gettime() до и после nanosleep(), и аргумент nanosleep.

gv
() автор топика
Ответ на: комментарий от anonymous

что то мне кажется сильно надуманым

Да, это модельная ситуация (искусственная нагрузка). Мне просто интересно, какие есть решения таких проблем о общем случае, или почему их нет.

gv
() автор топика
Ответ на: комментарий от gv

Гипотетический механизм, озвученный в топике, чем-то плох концептуально или просто не был никем реализован?

(функция вроде sendto_at(buffer, address, time))

Это не механизм, а кнопка «сделать збс».

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

случайной задержки (~5мс)

что то мне кажется сильно надуманым

У ТС может быть хилый встроенный процессор.

tailgunner ★★★★★
()
Ответ на: комментарий от gv

Записываю clock_gettime() до и после nanosleep(), и аргумент nanosleep

не делай так.

разверни nanosleep под свои нужды: надо считать таймер, вычислить время когда «проснуться», взять пакет из очереди,установить таймер, уйти в wait и наконец-то отправить. Будет точнее, хотя подводных камней хренова гора.

// псевдокод
while (1) { 
 tm=clock_gettime();
 buf = queue.poplast(); //< это время у тебя терялось
 if (!buf) continue;
 tm+=min;
 setitimer(ITIMER_REAL,tm);
 wait();
 send(buf);
}
MKuznetsov ★★★★★
()
Последнее исправление: MKuznetsov (всего исправлений: 1)
Ответ на: комментарий от MKuznetsov

не делай так

Я имел в виду, что clockget_time() вызываю для профилировки.

Код примерно такой:

for (;;) {
  buf = pop();
  time = calc_time(buf);

  a = clock_gettime();
  sleep(time);
  b = clock_gettime();

  log(a, b, time);
}
gv
() автор топика
Ответ на: комментарий от tailgunner

Это не механизм, а кнопка «сделать збс».

Ну, проблема ведь более-менее общая — IO, привязанный ко времени?

Я подумал, что возможно есть и какие-то решения для IO.

gv
() автор топика
Ответ на: комментарий от anonymous

полистайте sleep vs nanosleep

Спасибо, будем читать.

gv
() автор топика
Ответ на: комментарий от gv

Ну, проблема ведь более-менее общая — IO, привязанный ко времени?

Общая, да. Для ее решения есть таймеры и вызовы IO.

sleep(time);

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

tailgunner ★★★★★
()
Ответ на: комментарий от gv

Я имел в виду, что clockget_time() вызываю для профилировки.

вызывай его чтобы отметить «начало» твоего таймаута в 50ms, который ставится непосредственно ПЕРЕД pop() а не после него. Так ты уйдёшь от непредсказуемых задержек (может так приоритетная очередь, или изъятие единственного элемента значительно больше типового)

MKuznetsov ★★★★★
()
Ответ на: комментарий от tailgunner

Общая, да. Для ее решения есть таймеры и вызовы IO.

Ок.

sleep(time);

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

Не понял, к чему этот вопрос. В RT ведь будет тот же sleep. Вы предлагаете использовать что-то вместо sleep?

gv
() автор топика
Ответ на: комментарий от MKuznetsov

вызывай его чтобы отметить «начало» твоего таймаута в 50ms, который ставится непосредственно ПЕРЕД pop() а не после него.

Я наверное непонятно объяснил что делает calc_time(). Можно считать, что она возвращает абсолютное время, которое известно заранее и не зависит от того, когда был вызван pop(). Можно считать, что pop() вообще нет, отправляем нули.

gv
() автор топика
Ответ на: комментарий от gv

Я наверное непонятно объяснил что делает calc_time(). Можно считать, что она возвращает абсолютное время, которое известно заранее и не зависит от того, когда был вызван pop(). Можно считать, что pop() вообще нет, отправляем нули.

тогда вообще непонятно зачем тебе sleep/nanosleep. Получил время, взвёл таймер, лёг баиньки :) вызвал send() прям из сигнала.

PROFIT

MKuznetsov ★★★★★
()
Ответ на: комментарий от gv

Не понял, к чему этот вопрос

Вопрос был риторический.

Вы предлагаете использовать что-то вместо sleep?

Я даже не знаю, что уже предлагать. Нужные ключевые слова уже сказаны: SCHED_RR, таймеры. Если ты путаешь таймеры и sleep, предлагаю учить матчасть.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Я даже не знаю, что уже предлагать. Нужные ключевые слова уже сказаны: SCHED_RR, таймеры.

Да не надо предлагать, я в курсе про SCHED_RR, а топик создал, чтобы узнать, не придумали ли что-то еще.

Если ты путаешь таймеры и sleep, предлагаю учить матчасть.

Неудобная у вас форма общения, каждый раз надо думать, что же вы имеете в виду.

Может быть путаю. Я знаю одно отличие: аргумент для sleep() будет вычислен с ошибкой из-за того что неизвестно точное текущее время. Вы об этой ошибке или еще о чем-то?

gv
() автор топика
Ответ на: комментарий от MKuznetsov

тогда вообще непонятно зачем тебе sleep/nanosleep. Получил время, взвёл таймер, лёг баиньки :) вызвал send() прям из сигнала.

А в чем принципиальная разница?

gv
() автор топика
Ответ на: комментарий от gv

я в курсе про SCHED_RR, а топик создал, чтобы узнать, не придумали ли что-то еще.

Кроме таймеров и SCHED_RR - ничего.

Неудобная у вас форма общения

Это да.

Может быть путаю. Я знаю одно отличие: аргумент для sleep() будет вычислен с ошибкой из-за того что неизвестно точное текущее время. Вы об этой ошибке или еще о чем-то?

Я о том, что таймеры и sleep - это две очень разные вещи. В sleep никто не заморачивается точным контролем времени (в usleep тоже). Для точного отслеживания событий по времени есть таймеры, привязанные к нужным клокам, и тот самый SCHED_RR (и SCHED_FIFO), о котором ты в курсе. man timer_create, man timerfd_create.

tailgunner ★★★★★
()
Ответ на: комментарий от gv

в том что это решает твою задачу - отправить UDP в строго заданное время с минимальной погрешностью. Далее уменьшать погрешность можно только планировщиком и приоритетами.

MKuznetsov ★★★★★
()
Ответ на: комментарий от tailgunner

Кроме таймеров и SCHED_RR - ничего.

Понятно, спасибо.

Я о том, что таймеры и sleep - это две очень разные вещи. В sleep никто не заморачивается точным контролем времени (в usleep тоже). Для точного отслеживания событий по времени есть таймеры, привязанные к нужным клокам, и тот самый SCHED_RR (и SCHED_FIFO), о котором ты в курсе. man timer_create, man timerfd_create.

Но без SCHED_RR проблемы у, например, timerfd и clock_nanosleep (с TIMER_ABSTIME) одинаковые, а с SCHED_RR они должны работать одинаково хорошо?

gv
() автор топика
Ответ на: комментарий от MKuznetsov

в том что это решает твою задачу - отправить UDP в строго заданное время с минимальной погрешностью. Далее уменьшать погрешность можно только планировщиком и приоритетами.

Я вечером проведу тесты, но пока не вижу разницы между sendto сразу после sleep, либо сразу после wait, либо в обработчике сигнала.

gv
() автор топика
Ответ на: комментарий от gv

Но без SCHED_RR проблемы у, например, timerfd и clock_nanosleep (с TIMER_ABSTIME) одинаковые, а с SCHED_RR они должны работать одинаково хорошо?

Если коротко, то нет. Если длинно - да, если ты не сделаешь каких-нибудь малозаметных и распространенных ошибок.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Если коротко, то нет. Если длинно - да, если ты не сделаешь каких-нибудь малозаметных и распространенных ошибок.

Угум, мысль я понял.

gv
() автор топика

По поводу clock_nanosleep vs nanosleep vs select: видимо в современном Linux разницы нет и все три могут работать на интервалах меньше HZ.

gv
() автор топика

MKuznetsov

тесты провёл, или забил на эту фигню?

Ага, вот они:

timers.c timers.py

gcc timers.c -lrt -O2 -o timers

Шаг 10us:

./timers 10 10000 | ./timers.py

http://enise.org/~victor/share/10us.png

Шаг 100us:

./timers 100 10000 | ./timers.py

http://enise.org/~victor/share/100us.png

Шаг 1000us:

./timers 1000 1000 | ./timers.py

http://enise.org/~victor/share/1000us.png

На графиках diff временных меток, которые должны идти равномерно с интервалом 10, 100, 1000

gv
() автор топика
Последнее исправление: gv (всего исправлений: 2)
Ответ на: комментарий от gv

чё-то сегодни голова уже не работает :(

и как интерпретировать это diff в качестве оценки методов?

то что при больших таймаутах все методы примерно одинаковы видно.

любопытно конечно ещё глянуть те-же графики с другим планироващиком.

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

и как интерпретировать это diff в качестве оценки методов?

Ну, результаты в общем-то очевидные :)

При больших интервалах — пофиг (у меня >= 1мс).

При маленьких интервалах ошибка при timer_create или clock_nanosleep(TIMER_ABSTIME) одинаковая и меньше, чем при использовании *sleep() с относительным временем.

то что при больших таймаутах все методы примерно одинаковы видно.

Ага.

любопытно конечно ещё глянуть те-же графики с другим планироващиком.

Тоже ожидаемо. Ошибка в *sleep() с относительным аргументом остается, а ошибка у timer_create и clock_nanosleep(TIMER_ABSTIME) уменьшается.

Чем больше интервал, тем меньше влияет наличие SCHED_RR.

Для 10 us:

sudo chrt 99 ./timers 10 10000 | ./timers.py

http://enise.org/~victor/share/10us_rr.png

gv
() автор топика

1) Linux не RTOS

2) Подозреваю, автору не нужен realtime, и даже 5мс задержка ему годится. Короче, обычное красноглазие на пустом месте.

lovesan ★★
()
Ответ на: комментарий от lovesan

1) Linux не RTOS

Ну да, см. первый пост:

Настойщий real-time не требуется.

gv
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.