# io_uring - third-rank async I/O syscall _Процессы и ресурсы · LinuxLab Knowledge Base_ **TL;DR:** io_uring - shared-memory очередь между userspace и kernel. SQE submit без syscall (с SQPOLL). CQE - completion. Поддержка read/write/accept/connect/recv/send. Заменяет aio+epoll для I/O-bound сервисов. CVE 2022-2024 - не enable бездумно. ## Зачем io_uring Async I/O в Linux исторически - боль: - **`select`/`poll`** - O(n) на каждый вызов, syscall на каждый event - **`epoll`** - быстрее, но всё равно syscall на каждое add/wait/operation; сами read/write остаются blocking или non-blocking-with-EAGAIN - **`aio`** (POSIX async I/O) - ограничен (only direct I/O без буфера), "тёмные углы" в kernel, никогда не получил mainstream использование В 2019 Jens Axboe запушил **io_uring** в Linux 5.1: shared-memory ring-buffer между userspace и kernel. App кладёт submission entry в ring, kernel обрабатывает, completion отдаёт в другой ring. **Один syscall может subm'ить тысячи операций.** С 5.6 поддержка большинства syscall'ов (read, write, recv, send, accept, connect, openat, statx, fsync, splice, и др). Производительность: - **3-5x throughput** vs epoll+blocking I/O на mixed workload - **0 syscall'ов в hot path** при SQPOLL - **Linear scale** до миллионов IOPS на NVMe Applications: высоконагруженные сервера (proxy, load balancer), базы данных (PostgreSQL планирует, RocksDB), HFT, file copy (`liburing` cp). ## Архитектура - два ring'а ``` ┌─────────────┐ SQE ┌─────────────┐ │ app │ ────► │ submission │ ──┐ │ │ │ queue (SQ) │ │ │ │ └──────────────┘ │ │ │ ┌──────────────┐ ▼ │ │ ◄──── │ completion │ kernel processes │ │ CQE │ queue (CQ) │ async │ │ └──────────────┘ │ └─────────────┘ │ ▲ │ └───────────────────────────────────┘ enqueue CQE ``` - **SQ (Submission Queue)** - app пишет SQE (Submission Queue Entry) с описанием операции (op-code, fd, offset, buffer, length, ...) - **CQ (Completion Queue)** - kernel пишет CQE (result, user_data correlation) - Обе очереди - **mmap'нутые** (см. [mmap](/kb/mmap.md)) shared между app и kernel, нет копирования Размер ring'а - степень двойки до 32K (configured при `io_uring_setup`). ## Базовый flow 1. `io_uring_setup(entries, ¶ms)` → `fd` 2. `mmap(fd, ...)` ringы в userspace 3. App кладёт SQE в SQ, увеличивает SQ tail 4. `io_uring_enter(fd, num_to_submit, ...)` (или ничего, если SQPOLL) 5. Kernel обрабатывает - результаты в CQ 6. App читает CQE, увеличивает CQ head, освобождает буферы Ручная работа сложна, обычно используют **liburing**: ```c #include struct io_uring ring; io_uring_queue_init(QUEUE_DEPTH, &ring, 0); struct io_uring_sqe *sqe = io_uring_get_sqe(&ring); io_uring_prep_read(sqe, fd, buf, sizeof(buf), 0); io_uring_sqe_set_data(sqe, my_request_ptr); io_uring_submit(&ring); struct io_uring_cqe *cqe; io_uring_wait_cqe(&ring, &cqe); printf("read %d bytes\n", cqe->res); io_uring_cqe_seen(&ring, cqe); ``` ## SQPOLL - syscall-less submit В SQPOLL mode kernel запускает kernel-thread, который пуллит SQ: ``` io_uring_queue_init_params(QD, &ring, &(struct io_uring_params){ .flags = IORING_SETUP_SQPOLL, .sq_thread_idle = 1000, // ms idle перед park }); ``` Теперь app может submit'ить вообще **без `io_uring_enter`** - просто заполнять SQE и обновлять SQ tail. Kernel-thread увидит, начнёт обрабатывать. Минусы: - Kernel-thread жрёт CPU когда busy-pollит - Требует CAP_SYS_NICE (раньше CAP_SYS_ADMIN) - На многопроцессорных машинах нужно pin thread к нужному CPU Используется в HFT, ultra-low-latency сервисах. ## Fixed buffers и fixed files По умолчанию каждый submit делает `get_user_pages` на буфер - дорого. **Fixed buffers**: pre-register пул буферов один раз: ``` io_uring_register_buffers(&ring, iovecs, num); io_uring_prep_read_fixed(sqe, fd, buf_idx, len, offset, buf_index); ``` Аналогично **fixed files**: `io_uring_register_files` - пул FD, меньше overhead на dup/close. Дают +20-30% throughput на heavy workload. ## Multi-shot и provided buffers Linux 5.18+ - **multi-shot accept**: один SQE accept'ит много соединений (CQE per accept). Полезно для серверов: ``` io_uring_prep_multishot_accept(sqe, listen_fd, NULL, NULL, 0); ``` **Provided buffers**: app даёт kernel пул буферов, recv выбирает свободный → меньше fragmentation памяти. Linux 5.19+ - **registered ring fd** (faster mmap-less ringы). ## io_uring vs альтернативы | Свойство | epoll + non-block | aio | io_uring | |----------|-------------------|-----|----------| | Syscall на event | да | да | нет (с SQPOLL) | | Buffered I/O | да (sync) | нет, только O_DIRECT | да | | sockets | да | плохо | да | | files | sync | да (O_DIRECT) | да (любые) | | Сложность API | средняя | высокая | средняя (с liburing) | | Many ops в одном вызове | нет | нет | до thousands | | Latency 99p | средняя | низкая | очень низкая | Реальный win io_uring: - **Mixed I/O**: socket + disk + open в одном цикле - **High concurrency**: 100K+ in-flight ops - **NVMe**: 3M+ IOPS реально достижимы Real-world adopters: ScyllaDB, Cloudflare proxies, Netty (Java), tokio-uring (Rust), libevent4. ## Security history - CVE'и io_uring - сложная kernel-feature, было много CVE: - **CVE-2021-3782** - DoS через async cancellation - **CVE-2022-1786** - heap-out-of-bounds в request queueing, privesc до root - **CVE-2023-2598** - fixed buffers race, info-leak - **CVE-2024-0582** - UAF при retry'ах Многие production-системы (Google, Docker default seccomp profile) блокируют io_uring syscall'ы. Docker с июня 2023 включил `io_uring_*` в default-deny seccomp - **в контейнерах не работает по умолчанию**. ChromeOS, Android, Snap - тоже block. Если ты не контролируешь workload (multi-tenant), оставь disabled. Disable kernel-wide: ``` sysctl -w kernel.io_uring_disabled=2 # 5.13+ ``` Через [seccomp](/kb/seccomp.md): ``` SCMP_ACT_ERRNO(EPERM) для io_uring_setup, io_uring_enter, io_uring_register ``` ## Когда что-то пошло не так - **EPERM на `io_uring_setup`** - sysctl `kernel.io_uring_disabled` или seccomp блокирует. Проверь Docker security-opts. - **`-EAGAIN` в CQE** - kernel не успел, retry submit. Поднять queue-depth, проверить SQ overflow. - **`-ECANCELED` массово** - таймауты ловят неудачно. Проверь `IORING_OP_LINK_TIMEOUT`. - **High CPU в SQPOLL** - thread не park'ится. Уменьши `sq_thread_idle`, или explicitly call `io_uring_enter(IORING_ENTER_SQ_WAKEUP)`. - **Verifier-style errors** на новых ядрах - feature support меняется. `io_uring_get_probe()` показывает supported ops. - **Memory leak** - забыл `io_uring_cqe_seen()`. CQE не освобождён - ring заполняется, всё стопится. - **Performance хуже epoll** - неправильный workload. На мало- concurrent или CPU-bound io_uring не ускоряет, добавляет complexity. ## Где почитать - https://kernel.dk/io_uring.pdf - оригинальная whitepaper Axboe - https://unixism.net/loti/ - "Lord of the io_uring" tutorial - https://github.com/axboe/liburing - reference impl + examples - https://man7.org/linux/man-pages/man7/io_uring.7.html - manpage ## Команды ```bash cat /proc/sys/kernel/io_uring_disabled ``` 0=enabled, 1=disabled-in-cgroup, 2=disabled-globally. Sysctl с 5.13+ ```bash perf trace -e 'io_uring_*' ``` Трассировать io_uring syscall'ы - смотреть что app submit'ит ```bash ls /sys/kernel/debug/tracing/events/io_uring/ ``` Tracepoint'ы io_uring для bpftrace/perf ```bash io_uring-test ``` Тулза из liburing-test pkg - микро-бенч SQ submission rate ```bash grep -r 'io_uring' /etc/docker/seccomp.json ``` Проверить Docker seccomp - блокирует ли io_uring (default-deny с 23.06) ```bash sysctl -w kernel.io_uring_disabled=2 ``` Disable io_uring на всей системе - hardening для multi-tenant ```bash strace -e trace=io_uring_setup,io_uring_enter ./myapp ``` Минималистично: только io_uring syscall'ы из приложения ```bash fio --ioengine=io_uring --rw=randread --iodepth=128 --size=10G --name=t ``` Бенчмарк io_uring через fio - сравнить с --ioengine=libaio/sync ## См. также - [mmap - файлы и shared memory](/kb/mmap.md) - [File descriptors (stdin, stdout, stderr)](/kb/file-descriptors.md) - [Page cache - диск в памяти](/kb/page-cache.md) - [Kernel modules - LKM, modprobe, signing, DKMS](/kb/kernel-modules.md) - [Сигналы (SIGTERM, SIGKILL, SIGHUP)](/kb/signals.md) - [Linux namespaces](/kb/namespaces.md)