Перейти к основному содержимому

Подсистема ввода-вывода в ОС

Разработчику Архитектору Инженеру

Ввод-вывод - диск, сеть и устройства

Процессор считает миллиарды операций в секунду, а диск отвечает за миллисекунды, сеть — за RTT в миллисекундах и выше. ОС существует во многом затем, чтобы скрыть эту медлительность — не блокировать всю машину на ожидании диска, переиспользовать каналы, ставить запросы в очередь.

Без понимания I/O непонятны:

  • почему read() "висит";
  • зачем буферизация и кэш страниц;
  • как SSD и NVMe менили планировщики;
  • связь прерываний с планированием CPU.

См. также: ядро — устройства, определение ОС, периферия в "Как работает компьютер".


Цель подсистемы I/O

ЗадачаКак решается
Единый интерфейсread/write/ioctl, сокеты, mmap
ЗащитаПриложение не пишет в порты диска напрямую
МультиплексированиеМного процессов — один контроллер
ЭффективностьDMA, очереди, асинхронный I/O
Обработка ошибокПовтор, таймаут, сообщение приложению

Слои — от приложения до устройства

┌─────────────────────────────────────┐
│ Приложение (cat, PostgreSQL, браузер) │
└─────────────────┬───────────────────┘
│ syscall: read, write, poll, io_uring
┌─────────────────▼───────────────────┐
│ Ядро: VFS (файлы), socket layer, │
│ block layer (диски), char devices │
└─────────────────┬───────────────────┘
│ вызовы драйвера
┌─────────────────▼───────────────────┐
│ Драйвер устройства (модуль ядра) │
└─────────────────┬───────────────────┘
│ MMIO, порты, DMA-дескрипторы
┌─────────────────▼───────────────────┐
│ Контроллер (AHCI, NVMe, NIC, USB) │
└─────────────────┬───────────────────┘

Физическое устройство

Драйвер — переводчик: "запиши блок LBA 42" → команды конкретному чипу. В Unix устройство часто видно как файл /dev/sda, /dev/tty, /dev/null — идея "всё есть файл" из Ядро операционной системы.


Классификация устройств

КлассПримерОбмен
Блок-ориентированныеHDD, SSD, лентафиксированные блоки, произвольный адрес (LBA)
Байт-ориентированныеклавиатура, мышь, часть сетевых адаптеровпоток байтов, без адресации блока
Смешанные / событийныетаймер, датчикисигнал о внешнем событии

Ниже "символьные" и "блочные" устройства — те же идеи в терминах Unix и Windows.


Доступ к регистрам контроллера

Контроллер устройства — набор регистров (команда, статус, данные). Программа обращается к ним двумя аппаратными способами:

СпособКакПлюсыМинусы
Отображение на память (MMIO)регистры — как ячейки RAM, команды MOVEудобно в C/C++, высокая скоростьнужно согласование скоростей, запрет кэша на этих адресах, "съедает" часть адресного пространства
Изолированное пространство I/Oкоманды IN / OUT x86проще железонужны ассемблерные вставки или функции RTL

Типичный пример MMIO — видеокарта. Классический PC-чипсет (северный/южный мост) как раз разводит эти два вида доступа.


Типы устройств

Символьные (character devices)

Поток байтов без фиксированных блоков — клавиатура, терминал, /dev/random, иногда принтер.

  • Доступ последовательный;
  • часто буфер в ядре невелик;
  • read может блокироваться до нажатия клавиши.

Блочные (block devices)

Данные адресуются блоками фиксированного размера (512 B, 4 KiB) — HDD, SSD, разделы.

  • Ядро может кэшировать блоки в page cache;
  • файловая система строится поверх блочного слоя;
  • см. файловую систему Windows.

Сетевые

Логически сокет — другой API (send/recv), но та же идея: syscall → стек ядра → драйвер NIC.


Программный, прерыванийный I/O и DMA

Programmed I/O (PIO)

CPU сам крутит цикл: "есть данные?" → прочитать байт → повторить. Просто, но дорого для больших объёмов — процессор занят.

Циклический опрос (polling) — частный случай PIO — команда пишется в регистр устройства, CPU в цикле читает регистр статуса, пока не появится признак "готово". Процессор и шина памяти простаивают неэффективно; зато не нужна таблица векторов прерываний.


I/O через прерывания

Устройство сигнализирует контроллеру прерываний (IRQ): "пакет готов", "сектор записан". CPU прерывает текущий процесс, ядро вызывает обработчик прерывания (ISR) — короткий, быстрый; тяжёлую работу откладывает в bottom half / workqueue / поток ядра.

При прерываниях: после команды устройству CPU может выполнять другую работу или ждать, не блокируя шину; по завершении операции устройство инициирует прерывание, ISR считывает данные из регистров и возвращает управление по сохранённому адресу команды.

Связь с процессами: пока ваш процесс ждёт диск, он в состоянии Waiting; планировщик отдаёт CPU другим — см. Жизненный цикл процесса в Linux.


DMA (Direct Memory Access)

Контроллер DMA переносит данные между устройством и RAM без участия CPU в каждом байте. CPU настраивает дескриптор (адрес, длина), уходит заниматься другим; по завершении — прерывание.

Современные NVMe и сетевые карты активно используют DMA — иначе гигабитная сеть съела бы весь CPU.

При DMA: контроллер сам ведёт обмен с устройством и RAM, по окончании пакета — прерывание CPU. Эффективнее чистых прерываний на больших блоках; шина может быть занята DMA, пока CPU выполняет другие инструкции.

Аналогия

PIO — вы сами носите коробки в грузовик.

DMA — вы наняли грузчиков (контроллер), а сами пьёте кофе; когда грузчики закончили — звонят (прерывание).


Стек программного обеспечения I/O

Снизу вверх (типичная схема подсистемы ввода-вывода):

┌─────────────────────────────────────┐
│ Приложение │
├─────────────────────────────────────┤
│ RTL — printf, scanf, iostream │ ← часто обёртки над API
├─────────────────────────────────────┤
│ API / системные вызовы │ ← CreateFile, read, write
├─────────────────────────────────────┤
│ Система буферизации (device-agnostic)│ ← единый размер блока, имена
├─────────────────────────────────────┤
│ Драйверы устройств │ ← регистры, мьютексы ядра
├─────────────────────────────────────┤
│ Обработка прерываний │
└─────────────────────────────────────┘
пользовательский режим ↑ | ↓ режим ядра

Система буферизации скрывает от приложения детали устройства — выделение блоков, очереди, ошибки, именование /dev/* или \\.\PhysicalDrive0.

В Windows приложение чаще вызывает Win32 API (CreateFile, ReadFile), а не syscall напрямую; в C/C++ printf/scanf идут через RTL и снова в API. Зная эту цепочку, можно уменьшить exe, линкуя только нужные API-функции без RTL.

Файловая система — следующий уровень абстракции над блочным I/O; см. Устройство файловой системы Windows.


Буферизация и кэш

Буфер в ядре

Двойная буферизация, кольцевые очереди — сглаживают разницу скоростей "производитель / потребитель".


Page cache (кэш страниц)

Чтение файла часто не идёт на диск: данные уже в RAM как страницы кэша. Запись может попасть в dirty pages и сброситься на диск позже (fsync, sync).

Это связывает I/O с виртуальной памятью и замещением страниц.


Spooling (спулинг)

Очередь заданий на медленное устройство (принтер): приложение "отдало" файл в spool, освободило себя; демон печати работает с принтером по одному. Разрушает условие deadlock "прямой захват принтера" — см. Тупики (deadlock) и защита от них.


Синхронный и асинхронный I/O

РежимПоведениеПример
Синхронныйread() блокирует поток до данныхПростой код
НеблокирующийO_NONBLOCK, poll/select/epollСерверы, event loop
Асинхронный (AIO)io_uring (Linux), IOCP (Windows)Высокая нагрузка на диск/сеть

Важно: "асинхронный" не значит "магически быстрее" — значит поток не ждёт в syscall; завершение приходит callback’ом или повторным read.


Планировщик ввода-вывода (диск)

Когда много процессов бьют в один диск, порядок запросов влияет на latency и throughput.

АлгоритмИдея
FCFSПо очереди
SSTFСначала ближайший сектор (головка диска) — starvation
SCAN / C-SCAN"Лифт" по цилиндрам
CFQ (старый Linux)Справедливость между процессами
mq-deadline, bfq, noneСовременные SSD/NVMe — другая физика

На SSD выигрывает случайный доступ; планировщик чаще упрощён (none на NVMe), упор на очередь глубины контроллера.

В справочнике Linux: /sys/block/<dev>/queue/scheduler — см. Справочник по Linux.


ioctl и управление устройством

Не всё укладывается в read/write. ioctl — расширенные команды — eject CD, настройка сетевого интерфейса, громкость, режим терминала.

Терминал: stty, raw/cooked mode — классический пример символьного устройства.


Ошибки и надёжность

  • EIO, ENOSPC, ETIMEDOUT — errno в POSIX.
  • Повтор при временных сбоях (сеть).
  • Журналирование ФС — целостность после обрыва питания — Устройство файловой системы Windows.
  • RAID, резервирование — уровень ниже ОС, но видно как одно устройство.

Пример — что происходит при cat file.txt

  1. Shell вызывает read() → syscall в ядро.
  2. VFS находит inode, page cache.
  3. Если страницы нет — page fault, чтение с диска через block layer.
  4. Драйвер ставит DMA-запрос; процесс блокируется (Waiting).
  5. Диск закончил → IRQ → драйвер будит процесс → данные в user buffer.
  6. Повтор до EOF; write() на stdout — тот же путь к терминалу/пайпу.

Связь с другими темами


Что запомнить

  1. I/O — слои и абстракции, не "магия драйвера".
  2. Прерывания + DMA освобождают CPU.
  3. Кэш делает повторное чтение файла быстрым.
  4. Планировщик диска всё ещё важен под смешанной нагрузкой; на NVMe — иная эра.
  5. Для серверов — epoll / io_uring / IOCP, не только синхронный read.

Чек-лист · Память · Замещение страниц


Практический сценарий диагностики I/O

Когда "все тормозит", но CPU не загружен:

  1. Проверяют latency диска и глубину очередей.
  2. Смотрят долю ожидания I/O у процессов.
  3. Выявляют, где синхронный ввод-вывод блокирует рабочие потоки.
  4. Переносят критичные операции на асинхронные механизмы и буферизацию.

Этот сценарий напрямую связывает разделы о процессах, памяти и файловой системе: Управление процессами в Linux, Механизмы распределения памяти в ОС, Устройство файловой системы Windows.