linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Уроки
  • How it works
  • Симулятор
  • База знаний
  • Собеседование
Index
Categories
All entries
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
home/linux/kb/Процессы и ресурсы/io-uring

kb/processes ── Процессы и ресурсы ── advanced

io_uring - third-rank async I/O syscall

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 бездумно.

view as markdownaka: iouring, io-uring, async-io, liburing

Зачем 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) shared между app и kernel, нет копирования

Размер ring'а - степень двойки до 32K (configured при io_uring_setup).

Базовый flow

  1. io_uring_setup(entries, &params) → 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 <liburing.h>
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-blockaioio_uring
Syscall на eventдаданет (с SQPOLL)
Buffered I/Oда (sync)нет, только O_DIRECTда
socketsдаплохода
filessyncда (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:

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

§ см. также

  • mmapmmap - файлы и shared memory`mmap()` маппит файл (или анонимный регион) в virtual address space процесса. Любые read/write через указатель = операции с файлом. Основа shared memory.
  • file-descriptorsFile descriptors (stdin, stdout, stderr)File descriptor - целое число, через которое процесс обращается к открытому файлу/сокету/pipe. У каждого процесса 0/1/2 = stdin/stdout/stderr.
  • page-cachePage cache - диск в памятиPage cache - кеш в RAM для содержимого файлов. Любое чтение/запись ФС идёт через него. «Used» в free выглядит большим, но page cache - это availabe.
  • kernel-modulesKernel modules - LKM, modprobe, signing, DKMSLKM - код, динамически загружаемый в kernel. modprobe резолвит зависимости через depmod. Подпись модуля для Secure Boot. DKMS пересобирает out-of-tree модули после kernel-upgrade. Lockdown mode запрещает загрузку неподписанных.
  • signalsСигналы (SIGTERM, SIGKILL, SIGHUP)Сигнал - асинхронное уведомление процессу от ядра или другого процесса. TERM - попроси завершиться, KILL - убей сразу, HUP - перечитай конфиг.
  • namespacesLinux namespacesNamespaces - механизм ядра, который даёт процессу собственный изолированный view на ресурс (сеть, mount-points, PID, UID, IPC, hostname, time). На них построены все контейнеры.
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки