Learning Platform
Глоссарий Troubleshooting
Урок 06.04 · 18 мин
Начальный
Real-timeSCHED_FIFOSCHED_RRSCHED_DEADLINELinux

Real-time классы Linux — SCHED_FIFO, SCHED_RR, SCHED_DEADLINE

«Real-time» — понятие, которое люди понимают как «быстро». Это неверно. В системном программировании real-time означает не быстро, а предсказуемо — с гарантированным верхним пределом задержки. ABS в машине должен срабатывать за 2 мс не потому, что 2 мс быстро, а потому, что гарантировано не позже — иначе колесо заблокируется, машина юзит.

В Linux есть три real-time scheduling класса: SCHED_FIFO, SCHED_RR, SCHED_DEADLINE. Они дают приложениям приоритет над обычными процессами, но требуют осторожности: SCHED_FIFO + бесконечный цикл = повисшая система. В этом уроке — обзор каждого класса, типичные ловушки, и почему для junior data engineer real-time почти никогда не нужен.


Hard real-time vs soft real-time

Сначала важное концептуальное различие.

Hard vs Soft real-time
Hard real-timeЖёсткий дедлайн. Если задача не выполнена за X мс -- катастрофа. Контроллер ABS, autopilot самолёта, медицинское оборудование. Linux НЕ подходит без RT-PREEMPT patch
Soft real-timeМягкий дедлайн. Желательно, чтобы задача укладывалась в X мс. Не катастрофа если иногда нет -- просто плохо. Видео-стриминг, VoIP, gaming
Linux real-timeDefault Linux подходит для soft real-time. Для hard real-time нужен PREEMPT_RT patch или специализированная RTOS (FreeRTOS, VxWorks, QNX)

Без специальных мер Linux может иметь latency-spikes до нескольких миллисекунд (interrupts, kernel locks, page faults). Для hard real-time это много. Соответственно:

  • Hard real-time — специализированные ОС (FreeRTOS, VxWorks, RTEMS, QNX) или Linux с PREEMPT_RT patch.
  • Soft real-time — стандартный Linux с RT scheduling классами.

Для junior data engineer почти всегда хватает обычного SCHED_OTHER (CFS/EEVDF). RT-классы нужны, если вы пишете audio framework, kernel video driver, или low-latency trading.


SCHED_FIFO — run until block

SCHED_FIFO — классический RT класс. Простая семантика: процесс работает, пока:

  1. Сам не заблокируется (sleep, read, mutex).
  2. Не появится готовый процесс с более высоким приоритетом.
  3. Сам не вызовет sched_yield.

Никаких time slices. Процесс не вытесняется по таймеру. Это и плюс (предсказуемо), и минус (бесконечный цикл = система виснет).

SCHED_FIFO -- работает до выхода или блокировки
Process A (FIFO 80)Real-time priority 80. Получает CPU, как только runnable
A runs...Работает без time slice. Не вытесняется обычными процессами или FIFO с меньшим приоритетом
B (FIFO 90) becomes runnableПоявился процесс B с более высоким FIFO приоритетом (90)
A preemptedA немедленно вытесняется -- более высокоприоритетный FIFO вытесняет
DANGER: infinite loopЕсли FIFO процесс зацикливается без блокировки, остальные процессы (включая обычные и FIFO с меньшим prio) не получат CPU. Возможно фризы системы

Защита от RT runaway:

# В sysctl есть параметры:
sysctl kernel.sched_rt_period_us
# 1000000 (1 second)

sysctl kernel.sched_rt_runtime_us
# 950000 (0.95 second)

# Это означает: за каждую секунду RT процессы (FIFO+RR) получат не больше 0.95 сек CPU.
# Если съели всё -- 50 мс отдадут обычным процессам. Это спасает от полного зависания.
# Запуск процесса в SCHED_FIFO (требует root):
sudo chrt -f 50 ./my-rt-program
# 50 -- RT priority (1-99)

# ВНИМАНИЕ: не запускайте CPU-bound бесконечный цикл!
# Test первым в виртуалке или с тестовой задачей

SCHED_RR — FIFO with time slice

SCHED_RR — то же самое, что SCHED_FIFO, но с time slice. Среди равноприоритетных RR-процессов кто-то отрабатывает свой slice и переходит в конец очереди равноприоритетных.

SCHED_RR -- round-robin между равноприоритетными
A (RR prio 50)RR процесс приоритета 50. Получает свой time slice (по умолчанию 100 мс)
B (RR prio 50)Другой RR процесс с тем же приоритетом. После A приходит B
C (RR prio 50)Третий RR того же приоритета
back to AПосле C снова A -- round-robin
D (RR prio 80)Если появится RR с более высоким приоритетом (80 > 50) -- вытеснит A/B/C немедленно. Round-robin только между равноприоритетными

Time slice для RR по умолчанию 100 мс, но можно поменять:

sysctl kernel.sched_rr_timeslice_ms
# 100

# Можно изменить:
sudo sysctl kernel.sched_rr_timeslice_ms=50
# Запуск в SCHED_RR:
sudo chrt -r 50 ./my-program

# Посмотреть текущий time slice процесса:
chrt -p 1234
# scheduling priority X
# (для RR можно увидеть slice через /proc/PID/sched -> policy = 2 = RR)

Когда выбирать FIFO vs RR:

  • FIFO — если у вас один RT процесс на priority level. Никакого round-robin не нужно. Простая семантика.
  • RR — если несколько процессов одного приоритета и хотите fairness между ними.

SCHED_DEADLINE — современный подход к RT

SCHED_DEADLINE появился в Linux 3.14 (2014). Совсем другой подход: вместо приоритетов задаём (runtime, deadline, period):

SCHED_DEADLINE -- задачи с deadlines
runtimeСколько CPU времени нужно задаче в каждый период. Например, 2 мс
deadlineК какому моменту от начала периода задача должна закончить runtime. Например, 5 мс
periodКак часто задача активируется. Например, каждые 10 мс
Example: video frame(runtime=2ms, deadline=5ms, period=10ms) -- каждые 10 мс надо обработать кадр, на это уйдёт 2 мс CPU, должно быть готово к 5 мс от начала кадра
EDF schedulerEarliest Deadline First. Из всех ready задач выбирается с самым близким deadline. Гарантирует schedulability при правильно заданных параметрах
Admission controlKernel проверяет, можно ли вообще выполнить задачу. Если суммарный runtime/period от всех DEADLINE задач превысит CPU bandwidth -- новая не примется

Главное преимущество DEADLINE:

  • Математически гарантированно, что задача успеет в deadline (если admission проверка прошла).
  • Admission control — система отклонит задачу, если её добавление сделает другие не выполнимыми. Это уникально для DEADLINE.
  • Не требует ручной настройки priority — задаёте через бизнес-параметры.
# SCHED_DEADLINE не поддерживается чрезвычайно простой chrt, нужны более низкоуровневые syscalls
# Документация: man sched_setattr
# Пример из ядра: tools/testing/selftests/sched/...

# В Python нужен ctypes для вызова sched_setattr:
# Полный пример выходит за рамки урока -- DEADLINE обычно используют в специализированных приложениях

Когда RT нужен в data engineering

Честный ответ: почти никогда.

Когда RT может пригодиться -- редкие случаи
Low-latency tradingАлготрейдинг: задержка в микросекунды критична. Но это специализированная индустрия, и обычно используются специализированные ОС или kernel bypass (DPDK, kernel bypass NICs)
Audio/video pipelinesALSA с low-latency, pro-audio (Jack). Аудио-обработка без glitches. SCHED_FIFO 80-90 для аудио-серверов
Industrial controlPLC, robotics, контроль механизмов. Чаще всего на отдельных RTOS, но иногда Linux с PREEMPT_RT
Game serversMMO с тиковой логикой. Хотя обычно достаточно nice -10 и быстрое железо
Data engineering: NOETL, batch processing, stream processing (Spark, Flink, Kafka) -- НЕ нужен RT. Throughput важнее latency consistency. Используйте SCHED_OTHER + nice/ionice

Spark task, Flink job, Kafka consumer, Postgres query — ни один из этих не требует RT. Они выиграют от больших cores и быстрого I/O, не от RT scheduling. RT scheduling даёт низкую максимальную задержку, не высокую throughput.

cgroups: контейнерный способ ограничить CPU-ресурсы

Анти-паттерн: «давайте сделаем все наши ETL процессы SCHED_FIFO, чтобы быстрее работали». Это даст:

  1. Не быстрее (наоборот — меньше преоптимизаций kernel).
  2. Риск powerlock всей системы.
  3. Сложности с другими процессами (ssh не достучаться, мониторинг не работает).

Ловушки real-time

Ловушка 1: priority inversion (уже разбирали в модуле 4)

Real-time процесс ждёт mutex, который держит обычный процесс. Решение: PTHREAD_PRIO_INHERIT.

Ловушка 2: page faults

RT процесс делает первый доступ к странице — page fault, kernel загружает страницу с диска — latency spike.

Решение: mlock() — pin страницы в RAM, или mlockall(MCL_CURRENT | MCL_FUTURE) для всей программы.

#include <sys/mman.h>
// В начале программы:
mlockall(MCL_CURRENT | MCL_FUTURE);
// Все текущие и будущие страницы будут pinned в RAM

Ловушка 3: kernel preemption

Даже обычный Linux может задержать RT процесс на несколько мс из-за non-preemptible kernel sections. Решение — PREEMPT_RT patch.

# Проверить preemption mode kernel:
cat /sys/kernel/realtime
# 0 -- обычный kernel
# 1 -- PREEMPT_RT (полностью preemptible)

# Или:
uname -v | grep -i preempt

Ловушка 4: bandwidth throttling

kernel.sched_rt_runtime_us ограничивает RT процессы до 95% CPU. Если RT процесс пытается забрать больше — его принудительно отключат на 50 мс. Это часто неожиданная задержка.

# Отключить throttling (опасно!):
sudo sysctl kernel.sched_rt_runtime_us=-1
# RT процессы могут забрать 100% CPU -- если зациклятся, ssh потеряете

Ловушка 5: CPU isolation

Для серьёзного RT нужно изолировать CPU от обычных задач:

# В /etc/default/grub:
# GRUB_CMDLINE_LINUX="isolcpus=4,5,6,7"
# После reboot CPU 4-7 не используются для обычных процессов
# Можно прибить RT процессы к ним через taskset

Это уже advanced настройка, обычному DE не нужна.


Реальный пример: Postgres backend и RT

Возьмём ситуацию: «наш Postgres медленный, давайте сделаем его SCHED_FIFO». Что будет?

  1. Postgres backend — это не один процесс, а десятки (по одному на коннект клиента). Сделать все RT — значит, ssh может перестать работать.
  2. WAL writer — да, мог бы получить выгоду от стабильной latency. Но он I/O-bound, RT не поможет ускорить fsync.
  3. Autovacuum — нет, это batch-задача, ей RT противопоказан.
  4. Background writer — то же, batch.

Правильный подход для Postgres — настроить cgroups (CPUWeight для приоритизации), правильный I/O scheduler (bfq или mq-deadline), shared_buffers + работа с памятью. Не RT.


Попробуй сам

# 1. Проверить текущие RT параметры:
sysctl kernel.sched_rt_period_us
sysctl kernel.sched_rt_runtime_us
sysctl kernel.sched_rr_timeslice_ms

# 2. Найти RT процессы в системе:
ps -eo pid,cls,rtprio,comm | awk '$2 ~ /FF|RR|DL/' | head
# CLS=FF -- SCHED_FIFO
# CLS=RR -- SCHED_RR
# CLS=DL -- SCHED_DEADLINE

# 3. Посмотреть latency-spikes -- классический cyclictest:
# (нужно установить rt-tests package)
# sudo cyclictest -t 4 -p 99 -i 1000 -l 10000
# Покажет min/avg/max latency между активациями таймера
# Без RT: max может быть 1000-10000 мкс
# С RT + isolcpus + mlockall: max может быть 50-100 мкс
# Hard RT (PREEMPT_RT): max <30 мкс

# 4. Безопасно поэкспериментировать с FIFO:
# Запускаем процесс, который сам sleep'ит -- не зависнет
sudo chrt -f 50 sleep 10 &
PID=$!
ps -p $PID -o pid,cls,rtprio,pri
# CLS=FF, RTPRIO=50
chrt -p $PID
# scheduling policy: SCHED_FIFO
# scheduling priority: 50
wait $PID

# 5. Посмотреть, что top показывает для RT процессов:
sudo chrt -f 80 sleep 100 &
PID=$!
top -p $PID
# Колонка PR покажет 'rt' или -101 (для prio 99 -> -100, для prio 80 -> -81)
# NI пустое (нет nice для RT)
kill $PID

# 6. CPU isolation проверить:
cat /sys/devices/system/cpu/isolated
# Если пусто -- ничего не изолировано. Если есть CPU -- isolcpus в kernel cmdline

Проверка знанийKnowledge check
Senior архитект говорит: 'Наш ML-inference сервис должен отвечать за 5 мс p99. Сейчас иногда вылетает на 50 мс. Может, перевести его в SCHED_FIFO -- решит проблему?'
ОтветAnswer
Скорее всего нет, и вот почему. Сначала надо понять, ОТКУДА 50 мс задержка: - Если это GC pause (например, JVM или Go) -- SCHED_FIFO не поможет. Решение: GC tuning (G1, ZGC в Java, GOGC=off в Go), pre-allocate memory, использовать другой runtime. - Если это page faults (первый доступ к странице, swap) -- SCHED_FIFO не поможет. Решение: mlockall(), disable swap. - Если это I/O wait (читали модель с диска) -- SCHED_FIFO не помогает с I/O. Решение: pre-load model in memory, использовать mmap. - Если это просто перегрузка CPU (несколько одновременных запросов на одно ядро) -- SCHED_FIFO может помочь маргинально, но cgroups CPU pinning лучше. - Если это interrupts (handlers IRQ блокируют CPU) -- SCHED_FIFO частично поможет (RT процесс preempts обычный), но не полностью. Решение: isolcpus + irqbalance. Что РЕАЛЬНО делать для low-latency inference: 1. Profile сначала. Используйте perf, eBPF, custom timing. Найдите, где именно 45 мс из 50 теряются. 2. Memory locking. mlockall(MCL_CURRENT | MCL_FUTURE) -- pin all pages в RAM. Page faults -- частая причина spike'ов. 3. CPU pinning. taskset для прибивания inference threads к dedicated cores. Эти cores не должны быть общими с другими сервисами. 4. CPU isolation. isolcpus=4-7 в kernel cmdline. Эти cores не используются для обычных процессов и interrupts. Inference там работает без помех. 5. NUMA awareness. На multi-socket системах -- модель в local memory CPU, который её обрабатывает. numactl --cpunodebind=0 --membind=0. 6. SCHED_FIFO -- только после всего выше. И только для real-time critical threads (inference loop), не для всего процесса (логирование, метрики могут оставаться normal). 7. Disable swap, disable transparent huge pages (THP). На low-latency серверах -- standard practice. 8. cgroups vs SCHED_FIFO. Часто cgroups + CPU pinning дают такой же результат с меньшим риском (нет runaway). 9. Specialized stacks. Для inference -- TensorRT, ONNX Runtime, специализированные библиотеки. Они сами оптимизированы для consistent latency. 10. Если до сих пор не хватает -- проверьте, нет ли pathological inputs (некоторые запросы в 10x больше остальных). Иногда p99 -- это про unusual workload, не про OS-level latency. Bottom line: SCHED_FIFO -- это последний шаг, не первый. И почти всегда есть менее рискованные способы достичь той же цели. Если вы пришли к RT-классам -- значит, всё остальное уже сделали правильно.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. Что означает 'real-time' в контексте Linux scheduling?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4