Последовательная связь с низкой задержкой в Linux

я реализую протокол через последовательные порты в Linux. Протокол основан на схеме ответа запроса, поэтому пропускная способность ограничена временем, необходимым для отправки пакета на устройство и получения ответа. Устройства в основном основаны на arm и работают под управлением Linux >= 3.0. У меня возникли проблемы с уменьшением времени поездки туда и обратно ниже 10 мс (115200 бод, 8 бит данных, без четности, 7 байт на сообщение).

какие интерфейсы ввода-вывода дадут мне самую низкую задержку: выбор, опрос, epoll или опрос вручную с помощью функции ioctl? Блокирует или не блокирует IO влияет на задержку?

Я попытался установить флаг low_latency с помощью setserial. Но, похоже, это не возымело никакого эффекта.

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

---- редактировать ----

серийный регулятор использует 16550A.

7 ответов


схемы запроса / ответа, как правило, неэффективны, и он быстро отображается на последовательном порту. Если вас интересует throughtput, посмотрите на оконный протокол, например, протокол отправки файла kermit.

теперь, если вы хотите придерживаться своего протокола и уменьшить задержку, Выберите, опрос, чтение даст вам примерно ту же задержку, потому что, как указал Энди Росс, реальная задержка заключается в обработке аппаратного fifo.

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

еще один вариант-заполнить ваш пакет, чтобы вы каждый раз попадали в порог fifo. Это также подтвердит, что если это или не проблема порога fifo.

10 msec @ 115200 достаточно для передачи 100 байтов (предполагая 8N1), поэтому то, что вы видите, вероятно, потому, что low_latency флаг не установлен. Попробуй!--3-->

setserial /dev/<tty_name> low_latency

он установит флаг low_latency, который используется ядром при перемещении данных вверх в слое tty:

void tty_flip_buffer_push(struct tty_struct *tty)
{
         unsigned long flags;
         spin_lock_irqsave(&tty->buf.lock, flags);
         if (tty->buf.tail != NULL)
                 tty->buf.tail->commit = tty->buf.tail->used;
         spin_unlock_irqrestore(&tty->buf.lock, flags);

         if (tty->low_latency)
                 flush_to_ldisc(&tty->buf.work);
         else
                 schedule_work(&tty->buf.work);
}

вызов schedule_work может отвечать за задержку в 10 мс, которую вы наблюдаете.


последовательные порты в linux "обернуты" в терминальные конструкции в стиле unix, что поражает вас с задержкой в 1 тик, т. е. 10 мс. Попробуйте if stty -F /dev/ttySx raw low_latency помогает, никаких гарантий.

на ПК, вы можете пройти хардкор и поговорить со стандартными последовательными портами напрямую, вопрос setserial /dev/ttySx uart none чтобы отключить драйвер linux от последовательного порта hw и управлять портом через inb/outb к регистрам порта. Я пробовал, он отлично работает.

недостатком является то, что вы не получаете прерываний, когда поступают данные, и вам нужно опросите регистр. часто.

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


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

--- редактировать ---

оказывается, я был абсолютно неправ. Все, что было необходимо, - это увеличить скорость тика ядра. По умолчанию 100 тиков добавили задержку 10ms. 1000Hz и отрицательный славный значение для последовательного процесса дает мне поведение времени, которого я хотел достичь.


ни один из этих системных вызовов не влияет на задержку. Если вы хотите читать и писать один байт как можно быстрее из userspace, вы действительно не собираетесь делать лучше, чем простой read()/write() пара. Попробуйте заменить последовательный поток сокетом из другого процесса userspace и посмотрите, улучшаются ли задержки. Если это не так, то ваши проблемы-скорость процессора и аппаратные ограничения.

вы уверены, что ваше оборудование может сделать это на всех? Это не редкость, чтобы найти UARTs с конструкция буфера, которая вводит много байтов латентности.


на этих скоростях линии вы не должны видеть задержки, которые велики, независимо от того, как вы проверяете готовность.

вам нужно убедиться, что последовательный порт находится в режиме raw (поэтому вы делаете "неканонические чтения") и что VMIN и VTIME установлены правильно. Вы хотите убедиться, что VTIME равен нулю, чтобы таймер между символами никогда не срабатывал. Вероятно, я бы начал с установки VMIN на 1 и настроился оттуда.

накладные расходы syscall ничто по сравнению с временем на провод, поэтому выберите () против poll () и т. д. вряд ли это что-то изменит.


вот что setserial делает, чтобы установить низкую задержку в файловом дескрипторе порта:

ioctl(fd, TIOCGSERIAL, &serial);
serial.flags |= ASYNC_LOW_LATENCY;
ioctl(fd, TIOCSSERIAL, &serial);

короче говоря: используйте USB-адаптер и ASYNC_LOW_LATENCY.

Я использовал USB-адаптер на основе FT232RL на Modbus на 115.2 kbs. Я получаю около 5 транзакций (до 4 устройств) примерно за 20 мс с ASYNC_LOW_LATENCY. Это включает в себя две транзакции на медленное устройство (время отклика 4 мс). Без ASYNC_LOW_LATENCY общее время составляет около 60 мс. С адаптерами USB FTDI ASYNC_LOW_LATENCY устанавливает межсимвольный таймер на самом чипе на 1 мс (вместо 16 мс по умолчанию). Я в настоящее время используется домашний USB-адаптер, и я могу установить задержку для самого адаптера на любое значение, которое я хочу. Установка его на 200 мкс бреет еще одну мс, что 20 мс.