Подсистема ввода-вывода в ОС
Ввод-вывод - диск, сеть и устройства
Процессор считает миллиарды операций в секунду, а диск отвечает за миллисекунды, сеть — за 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
- Shell вызывает
read()→ syscall в ядро. - VFS находит inode, page cache.
- Если страницы нет — page fault, чтение с диска через block layer.
- Драйвер ставит DMA-запрос; процесс блокируется (Waiting).
- Диск закончил → IRQ → драйвер будит процесс → данные в user buffer.
- Повтор до EOF;
write()на stdout — тот же путь к терминалу/пайпу.
Связь с другими темами
- Процесс ждёт I/O → не держит CPU → планирование.
- Дисковый swap → подкачка.
- Драйвер в kernel mode → сбой опасен в монолитном ядре → Ядро операционной системы.
- Блокировки на файлах → Тупики (deadlock) и защита от них.
Что запомнить
- I/O — слои и абстракции, не "магия драйвера".
- Прерывания + DMA освобождают CPU.
- Кэш делает повторное чтение файла быстрым.
- Планировщик диска всё ещё важен под смешанной нагрузкой; на NVMe — иная эра.
- Для серверов — epoll / io_uring / IOCP, не только синхронный
read.
Чек-лист · Память · Замещение страниц
Практический сценарий диагностики I/O
Когда "все тормозит", но CPU не загружен:
- Проверяют latency диска и глубину очередей.
- Смотрят долю ожидания I/O у процессов.
- Выявляют, где синхронный ввод-вывод блокирует рабочие потоки.
- Переносят критичные операции на асинхронные механизмы и буферизацию.
Этот сценарий напрямую связывает разделы о процессах, памяти и файловой системе: Управление процессами в Linux, Механизмы распределения памяти в ОС, Устройство файловой системы Windows.