Learning Platform
Глоссарий Troubleshooting
Урок 14.01 · 18 мин
Начальный
DevicesLinuxKernel/devBlock devicesCharacter devices

Block vs char devices — два мира /dev

Откройте ls /dev на любой Linux-машине, и вы увидите странный зоопарк. sda1, tty3, null, random, input/mouse0, nvme0n1p2. Что это такое? Это файлы? Не совсем. Это “ручки”, через которые userspace-программы говорят с железом. Их называют device nodes, и они — одна из самых красивых абстракций Unix: “всё есть файл”, даже мышь, диск и генератор случайных чисел.

В этом уроке разберём два больших мира /dev: block devices (диски, разделы, RAID, LVM) и character devices (терминалы, мыши, аудио, /dev/null, /dev/random). Поймём, чем они отличаются физически, как kernel понимает, какой драйвер вызывать через major/minor numbers, и зачем уметь читать ls -l /dev/.


Зачем устройство выглядит как файл

В классических ОС (DOS, ранний Windows) каждое устройство было особым — открываешь дискету через одни API, принтер — через другие. Unix в 1970-е принял радикальное решение: всё, что не процесс — файл. Диск — файл, который можно read()/write(). Терминал — тоже файл, можно read() нажатые клавиши, write() — символ появится на экране. Сетевая карта чуть выбилась (сокеты — отдельный API), но всё остальное действительно файлы.

Это даёт мощную вещь: одни и те же команды работают везде. cp file1 /dev/sda запишет содержимое в диск (не делайте этого!). cat /dev/urandom | head -c 16 прочитает 16 случайных байт. echo hello > /dev/tty3 — если на третьей виртуальной консоли сидит пользователь, он увидит “hello”.

Архитектура: userspace -> /dev -> kernel driver -> hardware
AppUserspace-программа: vim, python, dd, cat. Открывает файл /dev/sda1, /dev/tty, /dev/null. С точки зрения API -- никакой разницы с обычным файлом
open()
VFSVirtual File System в kernel: видит open() и смотрит на тип inode. Для устройств inode помечен как 'character' или 'block', хранит major/minor номера
Driver dispatchKernel по major number находит зарегистрированный драйвер. Major 8 = sd_mod (SCSI/SATA disks). Major 4 = tty. Major 1 = mem (null, zero, random)
DriverКонкретный драйвер: sd_mod, nvme, tty, e1000e. Знает, как общаться с железом через шины (PCIe, SATA, USB)
HardwareФизическое железо: SSD на NVMe, монитор, USB-клавиатура. Драйвер делает MMIO/DMA/IRQ, превращая хочу читать в реальные операции

Когда вы делаете open("/dev/sda1", O_RDONLY), kernel не открывает файл на диске. Он находит драйвер по major number и говорит ему: “клиент хочет читать тебя, готовься”. Дальше read() уходит прямо в драйвер, минуя файловую систему.


Block device: блоки по 4K, random access

Block device — это устройство, которое отдаёт данные блоками фиксированного размера (обычно 512 байт или 4K) и поддерживает random access (можно прыгнуть на любое смещение). Это классические “хранилища”: HDD, SSD, NVMe, USB-флешки, CD/DVD, loopback-устройства, LVM volumes, RAID arrays.

$ ls -l /dev/sda /dev/sda1 /dev/nvme0n1 /dev/nvme0n1p1
brw-rw---- 1 root disk    8,  0 May 18 09:12 /dev/sda
brw-rw---- 1 root disk    8,  1 May 18 09:12 /dev/sda1
brw-rw---- 1 root disk  259,  0 May 18 09:12 /dev/nvme0n1
brw-rw---- 1 root disk  259,  1 May 18 09:12 /dev/nvme0n1p1

Обратите внимание на первый символ: b. Это маркер block device. Дальше — права (rw для root и группы disk), потом два числа: major (8 для SCSI/SATA, 259 для NVMe) и minor (0, 1, 2 — номер раздела). И disk — это группа, члены которой могут читать-писать диск напрямую (опасный privilege!).

Главная особенность block-устройств — page cache. Когда вы читаете /dev/sda1, kernel не идёт сразу в диск — он сначала смотрит в page cache. Если страница уже в RAM (потому что её недавно читал другой процесс) — отдаёт оттуда. Write тоже идёт через cache — сначала в RAM, потом kernel асинхронно сбрасывает на диск. Это огромное ускорение, но и источник проблем (durability, fsync — модуль 10).

NOTE

На блочное устройство можно “положить” файловую систему: mkfs.ext4 /dev/sda1. После этого файлы внутри FS попадают на диск через VFS, который дёргает драйвер блочного устройства. Если же делать dd if=/dev/sda1 of=backup.img — читаются raw-блоки, минуя FS. Это и есть мощь Unix: один и тот же интерфейс read/write работает на двух уровнях.


Character device: байт за байтом, sequential

Character device — это устройство, которое отдаёт поток байт без понятия “блок” и часто без random access. Терминал, мышь, клавиатура, последовательный порт, /dev/null, /dev/random, аудио. Когда вы нажимаете клавишу, в драйвер прилетают конкретные байты (например, “27 91 65” для стрелки вверх) — они доступны через read(/dev/tty0).

$ ls -l /dev/tty0 /dev/null /dev/zero /dev/random /dev/urandom /dev/input/mouse0 2>/dev/null
crw--w---- 1 root tty       4,  0 May 18 09:12 /dev/tty0
crw-rw-rw- 1 root root      1,  3 May 18 09:12 /dev/null
crw-rw-rw- 1 root root      1,  5 May 18 09:12 /dev/zero
crw-rw-rw- 1 root root      1,  8 May 18 09:12 /dev/random
crw-rw-rw- 1 root root      1,  9 May 18 09:12 /dev/urandom
crw-r----- 1 root input    13, 32 May 18 09:12 /dev/input/mouse0

Первый символ — c. Это character device. И опять major/minor: major 1 — “memory” (null, zero, random живут в одном драйвере mem); major 4 — tty; major 13 — input subsystem.

Главное отличие от block: нет page cache. Каждый read() идёт прямо в драйвер. Это логично — ну какой смысл кэшировать вывод /dev/random или нажатие клавиши? Поэтому character devices обычно медленнее по latency, но bandwidth их редко интересует — мышь генерирует 100 байт в секунду.


Major и minor: как kernel диспатчит вызов

Major number — это идентификатор драйвера. Когда драйвер загружается (например, sd_mod для SCSI/SATA дисков), он регистрирует свой major у kernel: “я отвечаю за major 8”. С этого момента любой open() на устройство с major 8 уходит в этот драйвер.

Minor number — это конкретный инстанс внутри драйвера. Для дисков: minor 0 — /dev/sda, minor 1 — /dev/sda1, minor 16 — /dev/sdb, minor 32 — /dev/sdc. Драйвер сам решает, как интерпретировать minor.

Major/minor: диспатч от устройства к драйверу
/dev/sdaBlock device. Major 8 (SCSI/SATA). Minor 0 = весь диск. Когда программа делает open(/dev/sda), kernel смотрит на inode -> 'block, major=8' -> ищет драйвер с major 8
/dev/sda1Major 8, minor 1. Тот же драйвер, но minor=1 значит первый раздел диска sda. Драйвер sd_mod сам читает таблицу разделов и знает offset/size раздела
/dev/sdbMajor 8, minor 16. Второй физический диск. Шаг 16 потому что зарезервировано на partitions (sdb1-sdb15)
major lookup
sd_modДрайвер SCSI/SATA disks. Зарегистрирован у kernel с major 8. Принимает все read/write для устройств с major 8, по minor определяет на какое физическое устройство и offset
/dev/tty0Major 4 (tty). Совсем другой драйвер. Здесь minor 0 = первая виртуальная консоль
/dev/nullMajor 1 (mem). Драйвер 'memory devices' -- объединяет null, zero, random, urandom через minor numbers (3, 5, 8, 9)

Посмотреть, какие major зарегистрированы прямо сейчас:

$ cat /proc/devices
Character devices:
  1 mem
  4 tty
  5 /dev/tty
  7 vcs
 10 misc
 13 input
 21 sg
 29 fb
116 alsa
180 usb
189 usb_device
226 drm

Block devices:
  7 loop
  8 sd
  9 md
 11 sr
 65 sd
 66 sd
253 device-mapper
254 mdp
259 blkext

Это динамический список. Когда загружаете модуль (modprobe) — появится новый major. Когда выгружаете (rmmod) — исчезнет.


Файлы, которые не файлы: /dev/null, /dev/zero, /dev/random

Эти “псевдоустройства” живут в kernel и не имеют физического железа. Но они character devices с major/minor — и работают через те же абстракции.

/dev/null (major 1, minor 3) — “чёрная дыра”. read() всегда возвращает 0 (EOF). write() всегда успешен, но данные выбрасываются. Используется чтобы выкинуть вывод: command > /dev/null 2>&1.

/dev/zero (1, 5) — бесконечный нулевой поток. read() всегда возвращает запрошенное количество нулевых байт. Используется для создания пустых файлов нужного размера или для wipe данных: dd if=/dev/zero of=zerofile bs=1M count=100 — создаст 100 МБ нулей.

/dev/random (1, 8) — криптографический PRNG. До Linux 5.6 блокировал, если “энтропии не хватало”. С 5.6 ведёт себя как /dev/urandom.

/dev/urandom (1, 9) — криптографический PRNG, никогда не блокирует. Источник случайности для ssh-keygen, openssl, всего криптографического. По современному консенсусу — используйте urandom всегда.

# Создать файл 100 МБ нулей:
dd if=/dev/zero of=zeros.bin bs=1M count=100

# Прочитать 16 случайных байт в hex:
dd if=/dev/urandom bs=16 count=1 2>/dev/null | xxd

# Выкинуть весь вывод:
make 2>&1 > /dev/null

# Замерить скорость диска через /dev/zero (write benchmark):
dd if=/dev/zero of=/tmp/test bs=1M count=1024 conv=fdatasync
WARNING

dd if=/dev/zero of=/dev/sda — сотрёт ваш диск. dd if=/dev/sda of=/dev/sdb — забэкапит sda в sdb (точная копия байтов). Эти команды легко уничтожают данные — работайте под root только с двойной проверкой of=.


/dev/tty, /dev/pts: терминалы

Терминалы — отдельная вселенная. Когда вы логинитесь по SSH, ваша shell привязана к pseudo-terminal (pty), который представлен в /dev/pts/N. Когда вы переключаетесь Alt-F1…F6 в локальной консоли, вы переключаете виртуальные терминалы /dev/tty1/dev/tty6.

$ tty                     # на какой терминал привязан текущий процесс
/dev/pts/0

$ ls -l /dev/pts/
crw--w---- 1 lev tty 136, 0 May 18 09:30 /dev/pts/0
crw--w---- 1 lev tty 136, 1 May 18 09:31 /dev/pts/1

Major 136 — pty slave. Через эти файлы terminal emulator (gnome-terminal, alacritty, mosh) общается с shell. write(/dev/pts/0, "hello\n") — буквы появятся в окне.

Попробуйте: echo "Hello from other terminal" > /dev/pts/0 из другого окна — получатель увидит сообщение.


Попробуй сам

# 1. Посмотрите major/minor всех block-устройств:
ls -l /dev/sd* /dev/nvme* 2>/dev/null
# Первая буква 'b' -- block; колонки с числами -- это major,minor

# 2. Посмотрите registered drivers:
cat /proc/devices

# 3. /dev/null в действии:
echo "this disappears" > /dev/null
cat /dev/null         # ничего не выводит -- EOF сразу

# 4. /dev/zero в действии:
head -c 32 /dev/zero | xxd
# 00000000: 0000 0000 ...  -- 32 нулевых байта

# 5. Создайте свой character device через mknod (требует root):
sudo mknod /tmp/mynull c 1 3   # такой же major/minor как /dev/null
ls -l /tmp/mynull
echo "test" > /tmp/mynull       # работает как настоящий /dev/null
sudo rm /tmp/mynull

# 6. Узнайте свой текущий tty:
tty

# 7. Посмотрите все pty (другие активные сессии):
ls -l /dev/pts/

# 8. Отправьте сообщение другой сессии (нужно знать её tty):
# В другом окне: tty -> /dev/pts/1
# В первом окне: echo "Привет из tty 0" > /dev/pts/1

Главные различия в одной таблице

Block vs Character -- что важно помнить
Block (b)Фиксированный размер блока (обычно 512 или 4096 байт). Random access. Page cache. Подходит для файловых систем
Char (c)Поток байт. Sequential. Без page cache. Подходит для terminals, mice, /dev/null, audio
HDD, SSD, NVMeХранилища: жёсткие диски, SSD, NVMe-устройства. Major 8 (sda), 259 (nvme), 7 (loop)
tty, mouse, null, randomТерминалы (4, 136), input devices (13), psevdo devices (1: null, zero, random)
seek(), big I/OМожно прыгать на любой offset. Хорошо для big transfers (mmap, O_DIRECT). Драйвер часто использует DMA
без seek, small I/OЧасто без поддержки lseek. read() блокирует если нет данных (mouse не нажата). Маленькие частые операции

Когда вы дальше будете дебажить “почему диск медленно работает” — вы будете смотреть на /dev/sda и его очереди (urok 14). Когда “почему ssh-сессия зависла” — на /dev/pts/N. Знание, какой тип устройства перед вами, экономит время.

lsblk и mount: block devices глазами пользователя
Проверка знанийKnowledge check
Junior спрашивает: 'Я сделал echo привет > /dev/sda1 и теперь Linux не грузится. Что я сломал? И почему вообще была возможность писать туда?'
ОтветAnswer
Вы записали ASCII-строку 'привет\n' прямо на начало первого раздела диска /dev/sda1. Этот раздел -- скорее всего корневая ФС (ext4/xfs). На первых 1024 байтах раздела лежит SUPERBLOCK файловой системы -- структура с метаданными: тип ФС, количество inode, размер блока, ссылка на root inode, magic number. Ваш echo перезаписал эту структуру. При следующей загрузке kernel пытается mount /dev/sda1, читает superblock, видит 'это не ext4' (magic number сломан) -- и mount fails. Дальше -- kernel panic, потому что rootfs не монтируется. Почему вообще была возможность писать? Потому что вы запустили команду как root (или через sudo). Без root -- /dev/sda1 имеет права 'brw-rw---- root disk', значит писать может только root и члены группы disk. Junior на машине без root прав туда не запишет. Как чинить? Загрузиться с LiveCD, попробовать tune2fs -O '' /dev/sda1 (или e2fsck) -- иногда удаётся восстановить если backup-копии superblock не повреждены (ext4 хранит их в нескольких местах: cat /etc/mke2fs.conf или dumpe2fs). Урок: к /dev/sd* относиться с тем же страхом, что к гранате. dd if=... of=/dev/sd... -- дважды думать о флаге of=. И самое главное: НИКОГДА не запускать команды с >/dev/sd... под root, если только вы не точно знаете, что делаете. Production-инженеры обычно работают с дисками через утилиты (mkfs, parted, fdisk) -- они проверяют, не открыт ли уже диск, не смонтирован ли, и подтверждают опасные операции.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. В выводе `ls -l /dev/sda1` первый символ строки -- 'b'. Что это значит?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4