Безопасно ли использовать режим низкой задержки с последовательными портами Linux?

Безопасно ли использовать режим tty с низкой задержкой с последовательными портами Linux? Документировано, что функция tty_flip_buffer_push "должна не вызываться из контекста IRQ, если установлено port->low_latency. Тем не менее, многие низкоуровневые драйверы последовательного порта вызывают его из ISR независимо от того, установлен флаг или нет. Например, драйвер mpc52xx вызывает флип-буфер безоговорочно после каждого чтения из своего FIFO.

Следствием использования флип-буфера с малой задержкой в ​​ISR является то, что драйвер дисциплины линии вводится в контексте IRQ. Моя цель - получить задержку в одну миллисекунду или меньше при чтении с высокоскоростного последовательного порта mpc52xx. Установка low_latency достигает цели по задержке, но также нарушает задокументированное предварительное условие для tty_flip_buffer_push.


person joshuanapoli    schedule 19.03.2013    source источник
comment
Я использовал ядро ​​2.6.28, которое довольно старое. Флаг low_latency не нужен для более новых ядер. Драйвер прекратил использование delayed_work при передаче данных от драйвера нижнего уровня к драйверу линейной дисциплины. github.com/torvalds/linux/commit/   -  person joshuanapoli    schedule 19.03.2013
comment
Хотя это кажется ненужным, флаг low_latency все еще присутствует в последнем ядре Linux. Его по-прежнему опасно использовать со многими низкоуровневыми драйверами.   -  person joshuanapoli    schedule 19.03.2013


Ответы (2)


Этот вопрос был задан на linux-serial, пятница, 19 августа 2011 г..

Нет, низкая задержка в целом небезопасна.

Однако в частном случае 3.10.5 low_latency безопасен.

Комментарии выше tty_flip_buffer_push гласят:

«Эта функция не должна вызываться из контекста IRQ, если установлено значение port->low_latency».

Однако код (3.10.5, drivers/tty/tty_buffer.c) противоречит этому:

void tty_flip_buffer_push(struct tty_port *port)
{
    struct tty_bufhead *buf = &port->buf;
    unsigned long flags;

    spin_lock_irqsave(&buf->lock, flags);
    if (buf->tail != NULL)
            buf->tail->commit = buf->tail->used;
    spin_unlock_irqrestore(&buf->lock, flags);

    if (port->low_latency)
            flush_to_ldisc(&buf->work);
    else
            schedule_work(&buf->work);
}
EXPORT_SYMBOL(tty_flip_buffer_push);

Использование spin_lock_irqsave/spin_unlock_irqrestore делает этот код безопасным для вызова из контекста прерывания.

Существует тест для low_latency, и если он установлен, flush_to_ldisc вызывается напрямую. Это немедленно сбрасывает флип-буфер в линейную дисциплину за счет увеличения времени обработки прерывания. Процедура flush_to_ldisc также закодирована так, чтобы быть безопасной для использования в контексте прерывания. Я предполагаю, что более ранняя версия была небезопасной.

Если low_latency не установлено, вызывается schedule_work. Вызов schedule_work — это классический способ вызвать обработчик «нижней половины» из «верхней половины» в контексте прерывания. Это приводит к тому, что flush_to_ldisc вызывается из обработчика "нижней половины" на следующем тактовом импульсе.

Глядя немного глубже, и комментарий, и тест, кажется, находятся в оригинальном коммите Алана Кокса e0495736 для tty_buffer.c. Этот коммит был переписан более ранним кодом, поэтому кажется, что когда-то не было теста. Тот, кто добавил тест и сделал flush_to_ldisc безопасным для прерываний, не удосужился исправить комментарий.

Поэтому всегда верьте коду, а не комментариям.

Однако в том же коде версии 3.12-rc* (по состоянию на 23 октября 2013 г.) похоже, что проблема возникла снова, когда были удалены spin_lock_irqsave в flush_to_ldisc и добавлены mutex_locks. То есть установка UPF_LOW_LATENCY во флагах serial_struct и вызов ioctl TIOCSSERIAL снова вызовет «атомарное планирование».

Последнее обновление от сопровождающего:

On 10/19/2013 07:16 PM, Jonathan Ben Avraham wrote:
> Hi Peter,
> "tty_flip_buffer_push" is called from IRQ handlers in most drivers/tty/serial UART drivers.
> 
> "tty_flip_buffer_push" calls "flush_to_ldisc" if low_latency is set.
> "flush_to_ldisc" calls "mutex_lock" in 3.12-rc5, which cannot be used in interrupt context.
> 
> Does this mean that setting "low_latency" cannot be used safely in 3.12-rc5?

Yes, I broke low_latency.

Part of the problem is that the 3.11- use of low_latency was unsafe; too many shared
data areas were simply accessed without appropriate safeguards.

I'm working on fixing it but probably won't make it for 3.12 final.

Regards,
Peter Hurley

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


Обновление: 18 февраля 2014 г., ядро ​​3.13.2

Станислав Грушка написал:

Hi,

setserial has low_latency option which should minimize receive latency
(scheduler delay). AFAICT it is used if someone talk to external device
via RS-485/RS-232 and need to have quick requests and responses . On
kernel this feature was implemented by direct tty processing from
interrupt context:

void tty_flip_buffer_push(struct tty_port *port)
{
    struct tty_bufhead *buf = &port->buf;

    buf->tail->commit = buf->tail->used;

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

But after 3.12 tty locking changes, calling flush_to_ldisc() from
interrupt context is a bug (we got scheduling while atomic bug report
here: https://bugzilla.redhat.com/show_bug.cgi?id=1065087 )

I'm not sure how this should be solved. After Peter get rid all of those
race condition in tty layer, we probably don't want go back to use
spin_lock's there. Maybe we can create WQ_HIGHPRI workqueue and schedule
flush_to_ldisc() work there. Or perhaps users that need to low latency,
should switch to thread irq and prioritize serial irq to meat
retirements. Anyway setserial low_latency is now broken and all who use
this feature in the past can not do this any longer on 3.12+ kernels.

Thoughts ?

Stanislaw
person Jonathan Ben-Avraham    schedule 19.10.2013

Исправление было опубликовано на LKML для решения этой проблемы. Он удаляет общий код для обработки low_latency, но сохраняет параметр для использования низкоуровневыми драйверами.

http://www.kernelhub.org/?p=2&msg=419071

Я попытался принудительно установить low_latency в Linux 3.12 с последовательной консолью. Ядро было очень нестабильным. Если вытеснение было включено, оно зависало через несколько минут использования.

Так что ответ на данный момент - держаться подальше.

person proski    schedule 28.02.2014