Операционные системы
Ввод-вывод
Процессор в миллион раз быстрее жёсткого диска. Один случайный read с HDD = 10 миллионов инструкций CPU впустую. Сеть передаёт гигабайты, видеокарта рендерит 60 кадров в секунду, диск пишет логи - всё это I/O. Эффективное управление вводом-выводом отделяет тормозящую программу от молниеносной. Понимание I/O - это понимание того, почему системы работают быстро или медленно.
- **Почему игры грузятся быстрее на SSD?** HDD: seek 5мс × 10000 файлов = 50 секунд только на поиск. SSD: seek 0.1мс → та же операция за 1 секунду. Понимание I/O объясняет 50x разницу.
- **Как nginx обрабатывает 100000 соединений на одном сервере?** Секрет: epoll (асинхронный I/O). Один процесс ждёт событий от тысяч сокетов, CPU не блокируется. Без epoll понадобилось бы 100000 потоков - коллапс.
- **Почему серверы используют RAID?** Диск живёт в среднем 4 года. Сервер с 20 дисками переживает сбой каждые 2 месяца. RAID 6 позволяет работать при 2 мёртвых дисках - сервис не падает, данные сохранны.
Цели урока
- Различать device controllers, drivers, interrupts, DMA
- Сравнить I/O methods: programmed I/O, interrupt-driven, DMA по CPU overhead
- Знать disk scheduling: FCFS, SSTF, SCAN, C-SCAN, LOOK
- Понимать RAID levels: 0 (stripe), 1 (mirror), 5 (parity), 10 (stripe+mirror) и trade-offs
- Применять буферизацию: page cache, double buffering, scatter-gather I/O
Аппаратура ввода-вывода
**Устройства ввода-вывода (I/O devices)** - это глаза, уши и руки компьютера. Клавиатура, мышь, диск, сетевая карта, видеокарта - всё это I/O устройства. Процессор в 1000 раз быстрее самого медленного I/O устройства, поэтому эффективное управление I/O критично для производительности.
**Классификация I/O устройств:** 1. **Block devices** (блочные) - работают блоками данных: HDD, SSD, USB-флешки 2. **Character devices** (символьные) - потоковые данные: клавиатура, COM-порт, терминал 3. **Network devices** - сетевые карты (особый случай)
**Анатомия I/O устройства.** Каждое устройство состоит из двух частей: 1. **Контроллер** (hardware) - микросхема, управляющая устройством. У контроллера есть **регистры** для команд и данных. 2. **Драйвер** (software) - код в OS, который знает, как общаться с конкретным контроллером.
Пример: чтение с диска
Когда программа вызывает `read()`, происходит цепочка: 1. **Syscall** → ядро 2. **VFS** находит драйвер файловой системы 3. **Драйвер диска** пишет команду READ в регистр контроллера 4. **Контроллер** двигает головку диска, читает сектор 5. **Данные** передаются в память через DMA (Direct Memory Access) 6. **Прерывание** уведомляет CPU, что операция завершена
**Memory-Mapped I/O (MMIO).** Современные контроллеры устройств "видны" как участки памяти. Драйвер пишет в адрес 0xFEDC000 - на самом деле это регистр сетевой карты. CPU не различает - это просто store в память.
**Port-Mapped I/O (PMIO).** Альтернатива MMIO: специальные инструкции процессора `IN/OUT` (x86). Используется редко, в основном legacy-устройствами.
Чем Memory-Mapped I/O (MMIO) отличается от Port-Mapped I/O (PMIO)?
Методы управления I/O
Как процессор узнаёт, что устройство закончило операцию? Существует три основных метода: **Polling (опрос)**, **Interrupts (прерывания)**, **DMA (прямой доступ к памяти)**. Каждый метод - trade-off между простотой и эффективностью.
**1. Polling (программируемый опрос).** Самый простой метод: CPU в цикле проверяет статус устройства. "Готово ли? Нет. Ещё нет. Теперь да!" Просто, но расточительно - CPU занят бесполезным ожиданием.
**2. Interrupts (прерывания).** Устройство "стучится" к CPU через специальный сигнал, когда готово. CPU в это время занят другой работой. Прерывание приходит → CPU сохраняет контекст → обрабатывает → возвращается. Эффективно!
Прерывания как дверной звонок
Аналогия: ожидание курьера. **Polling** - каждые 10 секунд подходить к двери и смотреть в глазок: "Приехал? Нет. Приехал? Нет." Время уходит впустую. **Interrupt** - у двери звонок. Можно заниматься своими делами, а когда курьер приходит - звонок прерывает работу. Эффективнее! Так и CPU: прерывание позволяет работать, пока диск крутится.
**3. DMA (Direct Memory Access).** Проблема: даже с прерываниями CPU должен копировать данные из буфера устройства в память. Для больших объёмов (видео, сеть) это медленно. **DMA контроллер** копирует данные напрямую в память, CPU вообще не участвует!
DMA в видеокартах
Когда видеокарта рендерит кадр игры, она генерирует **гигабайты** данных в секунду (4K @ 60fps ≈ 1.5 GB/s). Если бы CPU копировал каждый пиксель, производительность упала бы в 10 раз. **DMA** позволяет GPU писать напрямую в видеопамять, минуя CPU. Поэтому игры работают плавно.
**Выбор метода I/O в зависимости от устройства:** - **Polling:** Простые embedded-системы, редкие события (кнопка нажата?) - **Interrupts:** Большинство устройств (клавиатура, мышь, таймеры) - **DMA:** Высокоскоростные устройства (диски, сеть, видео)
**Асинхронный I/O в Linux: select/poll/epoll.** Когда программа работает с тысячами сокетов (веб-сервер), блокирующий `read()` не подходит. **epoll** позволяет ждать событий от множества файловых дескрипторов одновременно.
Почему DMA (Direct Memory Access) критично важен для высокоскоростных устройств вроде сетевых карт 10 Gbit/s?
Алгоритмы планирования дисков
**Жёсткие диски (HDD)** - механические устройства. Головка чтения движется над вращающимися пластинами. Поиск данных (seek) - самая медленная операция (~5-10мс). Если приходят запросы на чтение секторов 10, 500, 25 - в каком порядке их выполнять? **Disk scheduling** оптимизирует порядок обслуживания.
**Параметры производительности HDD:** - **Seek time** (поиск) - движение головки к нужному цилиндру: 5-10 мс - **Rotational latency** (ожидание поворота) - ждём пока нужный сектор подъедет под головку: 4 мс (7200 RPM) - **Transfer time** (передача) - чтение данных: 0.1 мс для 4KB блока Seek time доминирует! Минимизация движения головки = максимум производительности.
**FCFS (First-Come, First-Served).** Простейший алгоритм: обрабатывать запросы в порядке поступления. Проблема: головка хаотично мечется по диску, большие seek time.
**SSTF (Shortest Seek Time First).** Выбирать ближайший запрос. Жадный алгоритм: локально оптимален, но может вызвать **starvation** (дальние запросы ждут бесконечно).
**SCAN (алгоритм "лифта").** Головка движется в одну сторону, обслуживая все запросы на пути, доходит до конца → разворачивается. Как лифт: едет вверх, останавливается на всех этажах, потом вниз.
**C-SCAN (Circular SCAN).** Улучшение SCAN: после достижения края головка мгновенно "телепортируется" в начало, начинает новый проход. Более равномерное время ожидания.
SCAN vs C-SCAN: аналогия с автобусом
**SCAN** - автобус ходит по кругу, но по одному маршруту туда, по другому обратно. Пассажир на конечной станции ждёт дольше. **C-SCAN** - автобус идёт только в одном направлении, на конечной быстро возвращается в начало (без остановок). Более справедливое распределение времени ожидания между всеми запросами.
**LOOK и C-LOOK.** Оптимизации SCAN/C-SCAN: головка не доходит до физического края диска (0 или 256), а разворачивается на последнем запросе. Экономия движения.
**Disk scheduling в эпоху SSD:** SSD (флеш-память) не имеет движущихся частей → seek time практически нулевой (0.1 мс). Порядок запросов не влияет на производительность! Современные OS используют **Deadline** или **BFQ (Budget Fair Queueing)** schedulers - они оптимизируют latency и fairness, а не движение головки.
Почему NVMe быстрее SATA
**SATA III:** Интерфейс для HDD, максимум 600 MB/s, одна очередь команд (32 запроса). Протокол оптимизирован под медленные диски. **NVMe (PCIe):** Разработан для SSD. Пропускная способность 3500 MB/s (PCIe 3.0 x4), **64000 очередей** по 64000 команд в каждой. Параллелизм + низкая latency (прямой доступ к CPU через PCIe). Поэтому NVMe SSD в 5-7 раз быстрее SATA SSD.
Какой алгоритм планирования диска гарантирует отсутствие starvation (вечного ожидания запросов)?
RAID: отказоустойчивость и производительность
**RAID (Redundant Array of Independent Disks)** - технология объединения нескольких дисков для увеличения надёжности, производительности или того и другого. Идея: используя 4 диска, можно создать систему, которая переживёт выход из строя любого диска (RAID 5), или читать данные в 4 раза быстрее (RAID 0).
**Основные уровни RAID:** - **RAID 0** (striping) - скорость, без отказоустойчивости - **RAID 1** (mirroring) - полное дублирование, 2x надёжность - **RAID 5** (striping + parity) - производительность + защита от 1 сбоя - **RAID 6** (double parity) - защита от 2 сбоев - **RAID 10** (1+0) - mirror of stripes, баланс
**RAID 0 (Striping): максимальная скорость, нулевая защита.** Данные разбиваются на блоки и распределяются по дискам. Файл 4MB при 4 дисках: каждый диск получает 1MB. Чтение/запись параллельно → скорость умножается на N. Но: если **любой** диск сломается - все данные потеряны.
RAID 0 для видеомонтажа
Профессиональный монтаж 4K видео требует скорости 500+ MB/s (несколько потоков одновременно). Один SSD даёт 500 MB/s, но для 8K нужно больше. **RAID 0 из 4 NVMe SSD** → 2000 MB/s. Данные не критичны (рабочие файлы, бэкап на сервере), важна скорость. Типичный use case для RAID 0.
**RAID 1 (Mirroring): полное дублирование.** Каждый байт записывается на 2 диска. Один диск умирает - данные на втором. Простая и надёжная схема. Недостаток: ёмкость делится пополам (2TB + 2TB = 2TB полезных).
**RAID 5 (Striping + Parity): баланс между производительностью и надёжностью.** Данные распределены по дискам (striping), плюс на каждый stripe вычисляется **контрольная сумма (parity)**, которая хранится на другом диске. Можно восстановить данные с любого сломанного диска по формуле: `D = A ⊕ B ⊕ P` (XOR).
RAID 5 в серверах
Типичный файловый сервер: 6 дисков по 4TB в RAID 5 → 20TB полезной ёмкости (вместо 24TB). Скорость чтения ~500 MB/s (почти как RAID 0), но с защитой: любой диск может сломаться, данные сохранны. Пока сломанный диск заменяется, система работает (degraded mode). После замены - автоматическое восстановление (rebuild).
**RAID 6 (Double Parity): защита от 2 сбоев.** Как RAID 5, но **два** parity блока на stripe (используют разные алгоритмы). Можно потерять любые 2 диска. Критично для больших массивов: rebuild RAID 5 может занять сутки, вероятность второго сбоя во время rebuild - высока.
**RAID 10 (1+0): Mirror of Stripes.** Сначала создаём пары зеркал (RAID 1), потом объединяем их в stripe (RAID 0). Например, 4 диска: (Диск1+Диск2) зеркало, (Диск3+Диск4) зеркало, два зеркала в RAID 0. Скорость чтения 4x, надёжность высокая, но ёмкость 50%.
**Hardware RAID vs Software RAID:** - **Hardware RAID:** Специальный контроллер с процессором, кешем, батарейкой (для защиты от сбоя питания). Не нагружает CPU, но дорого (500+). - **Software RAID:** Управляется OS (Linux mdadm, Windows Storage Spaces). Использует CPU для parity, но современные CPU справляются. Бесплатно, гибко. Тренд: Software RAID побеждает (ZFS, Btrfs встроенный RAID).
Ключевые идеи
- **I/O устройства - узкое место.** CPU в 1000+ раз быстрее диска. Эффективное управление I/O критично: DMA освобождает процессор от копирования данных, прерывания избавляют от бесполезного ожидания (polling). Без этого система тормозит.
- **Три метода управления I/O: Polling, Interrupts, DMA.** Polling - простой, но расточительный (CPU крутится в цикле). Interrupts - эффективный (CPU работает, устройство "стучится" когда готово). DMA - для больших объёмов (устройство пишет в память напрямую, CPU свободен).
- **Disk scheduling оптимизирует движение головки HDD.** FCFS хаотичен, SSTF жадный (starvation), SCAN как лифт (справедливый, нет starvation). Для SSD неважен (нет механики), но алгоритмы планирования остаются для fairness и latency.
- **RAID: trade-off между скоростью, ёмкостью, надёжностью.** RAID 0 - максимум скорости, нулевая защита. RAID 1 - простое зеркало. RAID 5 - баланс (производительность + защита от 1 сбоя). RAID 6 - для больших массивов (защита от 2 сбоев). RAID не заменяет backup!
Связанные темы
Ввод-вывод тесно связан с архитектурой OS и производительностью систем:
- Файловые системы — FS управляет метаданными и размещением данных на диске. Понимание I/O (блоки, scheduling) необходимо для понимания производительности FS: почему ext4 быстрее ext3, что такое journaling, как работает copy-on-write в Btrfs
- Виртуальная память — Page faults вызывают дисковый I/O (swap). Swap на HDD убивает производительность (5мс latency), на SSD терпимо. Memory-mapped files (mmap) превращают файловый I/O в работу с памятью - OS управляет загрузкой страниц
- Планирование процессов — Процесс блокируется на I/O (ждёт диск/сеть) → scheduler переключает на другую задачу. I/O-bound процессы (много I/O) требуют другой стратегии планирования, чем CPU-bound (много вычислений)
- Сетевое программирование — Сеть - это I/O. Те же принципы: блокирующий I/O (простой, медленный), неблокирующий (select/poll/epoll). Понимание асинхронного I/O необходимо для высокопроизводительных серверов (nginx, Node.js)
Вопросы для размышления
- Почему базы данных (PostgreSQL, MySQL) так чувствительны к скорости диска? Как понимание I/O поможет оптимизировать производительность БД?
- При проектировании системы для потокового видео (4K, 60fps): какие методы управления I/O предпочесть? Зачем нужен DMA? Почему RAID 0 может быть полезен?
- Чем отличается программирование для embedded-систем (микроконтроллеры) от desktop-приложений с точки зрения I/O? Почему на Arduino используют polling, а Linux сервер - epoll?
Связанные уроки
- os-09-filesystems — Файловая система строится поверх блочного I/O
- os-04-scheduling — I/O scheduling - тот же класс задач, что CPU scheduling
- os-18-io-advanced — io_uring, async I/O, vectored I/O - продвинутые техники
- db-11-query-optimization