Проблема: один процесс — тысячи соединений
Базовая модель ввода/вывода UNIX
Блокирующий и неблокирующий ввод/вывод
Что такое мультиплексирование ввода/вывода
select — первый универсальный механизм
poll — удобнее, но всё ещё линейно
Почему select и poll плохо масштабируются
epoll — событийная модель Linux
Event loop на epoll
Почему epoll выбирают для серверов
io_uring — следующий этап
epoll и io_uring: главное различие
Сравнение механизмов
Эволюция механизмов
Выводы
1.82M

Мультиплексирование_ввода_вывода_шаблон_МИФИ

1.

Мультиплексирование ввода/вывода:
select/poll → epoll/io_uring
Волошина Юлия С25-702
09.10.2026

2. Проблема: один процесс — тысячи соединений

10 000+
1 поток
4 этапа
одновременных соединений —
проблема C10K
должен уметь ждать много
источников
select → poll → epoll → io_uring
Где применяется
Веб-серверы и обратные прокси (Nginx)
Базы данных и кэши (Redis)
Платформы исполнения (Node.js, libuv)
Брокеры сообщений и сетевые шлюзы
2

3. Базовая модель ввода/вывода UNIX

Процесс
Ядро
(пользовательский
режим)
системный вызов
read/write
Устройство / сокет /
канал
Что даёт дескриптор
В чём проблема
• единый интерфейс: open, read, write, close
• работает для файлов, каналов, сокетов
• основа для всех механизмов ожидания
• read() блокирует процесс, если данных нет
• один поток — одно ожидание
• тысяча клиентов — тысяча потоков?
По Робачевскому: открытый файл или сокет получает дескриптор, и именно он используется в
последующих операциях чтения и записи.
3

4. Блокирующий и неблокирующий ввод/вывод

Блокирующий режим
Неблокирующий режим (O_NONBLOCK)
• read/recv вызван, данных нет
• процесс переходит в состояние ожидания
• простой код, но один клиент на поток
• вызов возвращается сразу
• если не готово — ошибка EAGAIN
• опрос в цикле = busy-wait
Идея: не ждать один дескриптор, а спросить ядро — какие из
многих готовы к работе?
4

5. Что такое мультиплексирование ввода/вывода

Event loop
Два подхода к серверу
1. Зарегистрировать интерес (чтение/запись)
2. Ждать: ядро блокирует процесс до события
Поток на соединение
память на стек каждого потока
переключения контекста
плохо масштабируется
3. Получить список готовых дескрипторов
Один event loop
4. Обработать только готовые
один поток ждёт много дескрипторов
ядро говорит, кто готов
основа Nginx, Redis, Node.js
5. Вернуться к ожиданию
5

6. select — первый универсальный механизм

Ограничения
• FD_SETSIZE (обычно 1024)
• наборы пересоздаются перед каждым
вызовом
• O(n): перебор всего диапазона
• копирование наборов user kernel
Плюс: максимальная переносимость между UNIX-системами.
6

7. poll — удобнее, но всё ещё линейно

select
poll
Структура
fd_set (маска)
массив pollfd
Лимит fd
FD_SETSIZE
нет
Пересборка входа
каждый вызов
не нужна
Сложность
O(n)
O(n)
Переносимость
очень высокая
POSIX
Общий недостаток: при каждом ожидании ядро проверяет весь переданный набор
дескрипторов
7

8. Почему select и poll плохо масштабируются

Условная работа ядра на один вызов ожидания
Сценарий
100000
• 10 000 соединений
• активны ~10 из них
10000
1000
100
Вывод
10
1
100
1 000
10 000
100 000
Цена ожидания должна зависеть
от числа событий, а не от числа
соединений.
число наблюдаемых дескрипторов
select / poll: проверяется весь набор
epoll: обрабатываются только готовые
8

9. epoll — событийная модель Linux

epoll_create1()
epoll_ctl()
epoll_wait()
создать epoll-объект в ядре
добавить / изменить / удалить
интерес
получить только готовые
события
Level Triggered (LT)
Edge Triggered (ET, EPOLLET)
• событие выдаётся, пока fd готов
• проще и безопаснее
• режим по умолчанию
• уведомление при смене состояния
• читать нужно до EAGAIN
• нужны неблокирующие сокеты
Список интересов хранится в ядре — не передаётся при каждом вызове
9

10. Event loop на epoll

accept()
O_NONBLOCK
epoll_ctl(ADD)
epoll_wait()
read / write
EAGAIN → ждать
10

11. Почему epoll выбирают для серверов

Меньше работы
Регистрация один раз
ядро возвращает только готовые события, а не
весь набор
epoll_ctl вместо передачи списка при каждом
вызове
Event-driven
Неблокирующие сокеты
естественная основа для асинхронной
архитектуры
хорошо сочетается с O_NONBLOCK
Используют: Nginx · Redis · libuv (Node.js)
11

12. io_uring — следующий этап

SQ — очередь запросов
Приложение
(user space)
Ядро Linux
обща я па мять: за просы и результаты без копирова ния
(kernel space)
CQ — очередь завершений
Операции
Преимущества
read / write, accept / connect
пакетная подача запросов
send / recv, timeout и другие
меньше переходов user
kernel
12

13. epoll и io_uring: главное различие

epoll — модель готовности
io_uring — модель завершения
«Сообщи, когда операция не будет
блокироваться».
«Вот операция — выполни её и сообщи, когда она
завершится».
После уведомления приложение само вызывает
read/write.
Ядро выполняет I/O и кладёт результат в CQ.
epoll
io_uring
Модель
readiness
completion
Системных вызовов на операцию
wait + read/write
батч запросов на один enter
Файловый ввод/вывод
не помогает
поддерживается
13

14. Сравнение механизмов

select
poll
epoll
io_uring
Появился
BSD, 1980-е
System V, 1980-е
Linux 2.5.44 (2002)
Linux 5.1 (2019)
Модель
readiness
readiness
readiness
completion
Лимит fd
FD_SETSIZE
нет
нет
нет
Вход каждый вызов
да
да
нет (в ядре)
нет (очереди)
Сложность ожидания
O(n)
O(n)
~O(готовые)
~O(готовые)
все UNIX
POSIX
только Linux
только Linux
Переносимость
14

15. Эволюция механизмов

1980-е
1980-е
2002
2019
select
poll
epoll
io_uring
простой и
переносимый
массив pollfd, без
лимита
интересы хранятся в
ядре
очереди SQ/CQ, батчи
лимит, O(n)
O(n)
только Linux
completion-модель
15

16. Выводы

1
Основа — дескрипторы, системные вызовы и неблокирующий I/O из
классического UNIX
2
select/poll: один процесс ждёт многих, но цена O(n)
3
epoll: интересы в ядре, масштабируется на тысячи соединений
4
io_uring: очереди запросов и завершений, completion-модель
От простого ожидания — к событийной и асинхронной обработке
16

17.

Национальный
исследовательский
ядерный университет
Спасибо за
внимание!
English     Русский Правила