Learning Platform
Глоссарий Troubleshooting
Урок 14.03 · 18 мин
Начальный
DriversKernel modulesLinuxmodprobelsmod

Что такое драйвер — kernel modules и hardware abstraction

Когда вы делаете cat /etc/hostname, происходит огромная цепочка: open(), VFS, ext4-driver читает inode с диска, scsi-driver посылает команду чтения SATA-контроллеру, SATA-контроллер дёргает физический SSD. Каждый “переходник” между уровнями — это драйвер. Без драйверов kernel бы умел общаться только с CPU и RAM — никакого диска, сети, экрана, клавиатуры.

В этом уроке: что такое драйвер физически, почему Linux — “monolithic but modular” kernel, как работают kernel modules (.ko файлы), и команды lsmod, modprobe, insmod, rmmod для управления ими.


Драйвер — кусок кода в kernel, говорящий с железом

Драйвер — это код, который знает протокол общения с конкретным железом и предоставляет kernel API. Драйвер NVMe знает: куда писать в регистры NVMe-контроллера, как организовать submission/completion queues, как читать сектор. Driver e1000e знает: как разговаривать с Intel 1Gbit Ethernet через DMA-кольца. Driver xpad знает: как декодировать USB-пакеты с Xbox-контроллера.

С точки зрения kernel, драйвер делает три вещи:

  1. Register. “Я драйвер для железа с этим vendor ID и product ID” — сообщает шине (PCI, USB).
  2. Probe. Когда железо обнаружено, kernel вызывает probe() драйвера — инициализация устройства.
  3. Provide ops. Драйвер регистрирует функции (file_operations или подобное), которые kernel вызовет при open/read/write/ioctl от userspace.
Драйвер -- слой между kernel API и hardware
Userspacecat, vim, python -- работают через стандартный POSIX API: open, read, write. Не знают про hardware
syscall
VFSVirtual File System в kernel. Принимает read(), решает, какому драйверу передать (по major number device node, или по типу файловой системы)
DriverКонкретный драйвер: NVMe, e1000e, ext4. Реализует file_operations: read/write. Знает специфику железа: регистры, DMA, прерывания
MMIO / DMA / IRQ
HardwareФизическое железо: NVMe SSD, NIC, GPU. Общается через PCIe (для большинства), USB, I2C, SPI

Кодом драйвера может быть, например (упрощённый псевдо-C для драйвера character device):

static int my_read(struct file *f, char __user *buf, size_t len, loff_t *off) {
    // Прочитать с железа в kernel buffer
    char data = read_from_device();
    // Скопировать в userspace
    copy_to_user(buf, &data, 1);
    return 1;
}

static struct file_operations my_fops = {
    .read = my_read,
    .write = my_write,
    .open = my_open,
};

// При загрузке:
register_chrdev(major, "my_device", &my_fops);

userspace делает read(fd, ...) — kernel находит этот fd привязан к /dev/my_device с major X, вызывает my_fops.read, оттуда read_from_device — регистры железа.


Linux kernel — monolithic, но modular

В мире OS-архитектур есть два полюса:

  • Monolithic kernel. Всё в одном адресном пространстве kernel: VFS, scheduler, драйверы. Плюсы: быстро (всё рядом, нет IPC между подсистемами). Минусы: упал драйвер -> упало ядро.
  • Microkernel. Минимальное ядро (IPC, scheduling). Драйверы — userspace-процессы, общаются с kernel через сообщения. Плюсы: упал драйвер — ядро живо. Минусы: медленнее (overhead на IPC).

Linux monolithic, но с подвохом — модульный. Драйверы компилируются в kernel modules — отдельные .ko-файлы. Можно загрузить в kernel в runtime через modprobe и выгрузить через rmmod. Это даёт гибкость microkernel (drivers загружаются по требованию) при производительности monolithic (загруженный модуль работает в kernel space).

# Все загруженные сейчас модули:
$ lsmod | head
Module                  Size  Used by
nvme                   57344  3
nvme_core             184320  4 nvme
e1000e                338944  0
i915                 4136960  18
snd_hda_intel          61440  1
btrfs                1843200  0
xfs                  2228224  0
ext4                  954368  3
fuse                  217088  9

В колонке Size — размер в байтах. Used by — сколько других модулей зависят от него (3 для nvme значит, что 3 другие модуля используют этот).


Где живут модули

Все собранные модули лежат в /lib/modules/$(uname -r)/:

$ ls /lib/modules/$(uname -r)/kernel/drivers/net/ | head
amd
arcnet
bonding
broadcom
cisco
dummy.ko
ethernet
hyperv
intel
...

$ find /lib/modules/$(uname -r) -name "nvme.ko*"
/lib/modules/6.5.0-21-generic/kernel/drivers/nvme/host/nvme.ko.zst

.ko (kernel object) — скомпилированный бинарь модуля. .zst (zstandard) — сжатый. Размер обычно 50-500 КБ для простых драйверов, мегабайты для сложных (i915 для Intel GPU).

Каждая kernel версия имеет свой /lib/modules/<version>/. Поэтому apt upgrade linux-image создаст новую версию ядра и новую папку с модулями. Старая остаётся (можно загрузиться, если новая упадёт).


modprobe vs insmod: загрузка модулей

Есть два способа загрузить модуль.

insmod — “тупо загрузи этот файл”:

sudo insmod /lib/modules/$(uname -r)/kernel/drivers/net/dummy.ko

Минусы: вы должны указать точный путь и сами установить зависимости (если модуль требует другой модуль — insmod не загрузит его).

modprobe — умный, понимает зависимости и алиасы:

sudo modprobe dummy
sudo modprobe e1000e

modprobe смотрит в файл /lib/modules/<version>/modules.dep (карта зависимостей, генерируется depmod) и автоматически загружает все нужные модули. Так что если у модуля есть зависимости — modprobe их сначала подгружает.

Также modprobe умеет:

  • Алиасы (alias snd-card-0 snd-hda-intel в /etc/modprobe.d/*).
  • Параметры (modprobe my_driver verbose=1).
  • Blacklist (запрет на автозагрузку: blacklist nouveau).

Конфиги modprobe — в /etc/modprobe.d/ и /usr/lib/modprobe.d/.


rmmod — выгрузить модуль

$ sudo rmmod dummy

# Если модуль used by кем-то:
$ sudo rmmod nvme_core
rmmod: ERROR: Module nvme_core is in use by: nvme

Нельзя выгрузить, если есть зависимости. Сначала надо выгрузить зависимые модули. Или использовать modprobe -r (умное удаление):

sudo modprobe -r nvme
# выгрузит nvme и nvme_core если nvme был последним пользователем
WARNING

ВНИМАНИЕ: выгружать модули в running production-системе опасно. Если выгрузить nvme на системе, где / на NVMe — IO зависнет, kernel может паниковать. Выгружать модули осмысленно: только те, которые не критичны (например, экспериментальный driver USB-устройства).


Информация о модуле: modinfo

$ modinfo nvme
filename:       /lib/modules/6.5.0-21-generic/kernel/drivers/nvme/host/nvme.ko.zst
version:        1.0
license:        GPL
author:         Matthew Wilcox <[email protected]m>
srcversion:     ABC123...
depends:        nvme-core
intree:         Y
name:           nvme
vermagic:       6.5.0-21-generic SMP preempt mod_unload modversions
parm:           use_threaded_interrupts:bool
parm:           use_cmb_sqes:use controller's memory buffer for I/O SQes (bool)
parm:           default_ps_max_latency_us:max power saving latency for new devices; use PM QOS to change per device (ulong)

Это даёт:

  • filename — где модуль лежит.
  • license — GPL обычно (нужно для использования некоторых kernel API). Проприетарные драйверы (Nvidia legacy) имеют другую лицензию — kernel пометит это.
  • depends — от каких модулей зависит.
  • vermagic — для какой версии kernel собран. Если не совпадает — модуль не загрузится (kernel ABI меняется).
  • parm — доступные параметры модуля.

Параметры модулей: настройка драйверов

# Передать параметр при загрузке:
sudo modprobe i915 enable_psr=0

# Сделать постоянным -- в /etc/modprobe.d/i915.conf:
options i915 enable_psr=0

# Посмотреть текущие параметры загруженного модуля:
$ cat /sys/module/nvme/parameters/use_threaded_interrupts
N

/sys/module/<name>/parameters/ — live-просмотр параметров. Некоторые параметры можно менять в runtime (echo в файл), другие — только при загрузке.


Кто и когда загружает модули автоматически

Большинство модулей в обычной системе загружаются автоматически:

  1. Hotplug + udev. Воткнули USB-устройство — kernel создал uevent, udev (или kmod) вызвал modprobe usb-storage. Драйвер загружен.
  2. Boot-time. initramfs (initrd) содержит модули для дисков/файловых систем, нужные чтобы смонтировать root FS. После mount root — остальные подгружаются по мере необходимости.
  3. systemd-modules-load.service. Читает /etc/modules-load.d/*.conf и /usr/lib/modules-load.d/*.conf, загружает что прописано. Полезно для серверных модулей, не привязанных к hardware (br_netfilter для k8s, nf_conntrack для firewall).

Посмотреть, кто загрузил данный модуль — сложнее. Можно dmesg | grep -i <module> — увидеть, когда был probe.


Built-in vs modular

Некоторые драйверы скомпилированы прямо в kernel image (vmlinuz) — “built-in”. Они грузятся при boot вместе с kernel и не могут быть выгружены. Другие — “modular” (.ko).

Решение built-in vs module принимается при сборке kernel:

  • Built-in (y в Kconfig) — всегда есть.
  • Module (m) — собирается отдельно, загружается по требованию.
  • Disabled (n) — нет.

Дистрибутивы обычно билдят почти всё как модули, а в kernel image оставляют только то, что нужно для boot (ext4, scsi/ahci/nvme базовый). Это уменьшает RAM (модуль занимает RAM только когда загружен).

Посмотреть config своей kernel:

# Часто доступен:
zcat /proc/config.gz | grep CONFIG_NVME
# CONFIG_NVME_CORE=m       (modular)
# CONFIG_NVME_TCP=m

# Или:
cat /boot/config-$(uname -r) | grep CONFIG_EXT4
# CONFIG_EXT4_FS=m

Если =y — built-in; =m — module; # ... is not set — disabled.


Дебаг: проблемы с модулями

Главные граблями:

1. modprobe говорит “Module not found”. Возможно, опечатка в имени. Проверьте find /lib/modules/$(uname -r) -name "<name>*".

2. modprobe говорит “version magic mismatch”. Модуль собран под другую kernel. Часто бывает после kernel upgrade без перезагрузки. Решение: reboot. Или dkms для пересборки driver под новый kernel.

3. Драйвер загружается, но устройство не работает. Смотреть dmesg — драйвер логирует ошибки initialization туда.

4. Driver A vs Driver B для одного устройства. Иногда два драйвера могут обслужить одно железо (e.g. nouveau vs nvidia для Nvidia GPU). Тогда — blacklist одного:

echo "blacklist nouveau" | sudo tee /etc/modprobe.d/blacklist-nouveau.conf
sudo update-initramfs -u
reboot

Попробуй сам

# 1. Все загруженные модули:
lsmod | wc -l        # обычно 50-150
lsmod | head

# 2. Информация о ключевом модуле:
modinfo $(lsmod | head -2 | tail -1 | awk '{print $1}')

# 3. Где живут все модули:
ls /lib/modules/$(uname -r)/kernel/
du -sh /lib/modules/$(uname -r)/
# обычно 200-500 МБ

# 4. Сколько модулей сейчас загружено:
lsmod | tail -n +2 | wc -l

# 5. Параметры текущего kernel:
ls /sys/module/
# каждая папка -- загруженный модуль или built-in subsystem

# 6. Сколько RAM занимают модули:
lsmod | awk 'NR>1 {sum += $2} END {print sum/1024/1024 " MB"}'

# 7. Безопасный тест загрузки/выгрузки (dummy module безвреден):
sudo modprobe dummy numdummies=2
ip link    # увидеть dummy0, dummy1
sudo rmmod dummy

# 8. Конфиг kernel (если доступен):
cat /boot/config-$(uname -r) | grep -c '=m'   # модулей в конфиге
cat /boot/config-$(uname -r) | grep -c '=y'   # built-in

Drivers and Linux kernel community

Один из странных фактов о Linux: 80% драйверов идут “in-tree” — прямо в исходниках kernel. Когда выходит новая kernel, обновляются и драйверы. Это и плюс (драйверы поддерживаются вместе с kernel), и минус (нужно убедить maintainer’а принять ваш driver — может занять годы).

“Out-of-tree” драйверы — те, что собираются отдельно. Примеры: Nvidia proprietary, ZFS, некоторые vendor drivers. Их недостаток — ломаются на новых kernel версиях, требуют пересборки (dkms помогает).

В общем, для Junior DE задача проста: понимать, что драйвер — это kernel module, уметь читать lsmod/modprobe/modinfo, и не пугаться dmesg-сообщений о probe/initialization.


Проверка знанийKnowledge check
Поставил Wi-Fi на сервер -- USB-донгл RTL8812BU. Linux не видит беспроводной интерфейс. lsusb показывает девайс. dmesg говорит 'usb 1-2: Device USB ID 0bda:b812'. Как разобраться?
ОтветAnswer
Классическая ситуация: kernel видит USB-устройство (USB-стек работает, потому что lsusb показывает; usb-storage и hid-generic тоже работают -- общий USB-driver), но конкретный драйвер для этого Wi-Fi-чипа не загрузился. Шаги дебага: 1. Проверить, есть ли driver в составе kernel: modinfo rtl8812bu -> если 'ERROR: Module rtl8812bu not found' -- драйвера нет в дистрибутиве. 2. Проверить дерево драйверов в /lib/modules для RTL-серии: find /lib/modules/$(uname -r) -name '*8812*' find /lib/modules/$(uname -r) -name 'rtl*' Часто видим rtl8821ae, rtw88, rtw89, rtw_8821a -- но не 8812bu. 3. dmesg более подробно: dmesg | grep -A2 -i '0bda:b812' Часто увидите 'no driver claimed device' или 'usb: usb-storage matches but not actually claims' -- подтверждение что нет драйвера. 4. Поиск драйвера. Для RTL8812BU стандартного in-tree драйвера в Linux ещё нет (на 2026). Нужно out-of-tree: - https://github.com/morrownr/88x2bu-20210702 (популярный fork Realtek референса) - DKMS-пакет на Ubuntu: apt search rtl8812bu, иногда есть rtl8812bu-dkms. 5. Установка через DKMS (DKMS пересоберёт драйвер автоматически на kernel upgrade): sudo apt install dkms build-essential linux-headers-$(uname -r) git clone https://github.com/morrownr/88x2bu-20210702 cd 88x2bu-20210702 sudo ./install-driver.sh # это поставит через dkms 6. После установки -- modprobe 88x2bu или reboot. Проверить: lsmod | grep 88x2bu dmesg | tail ip link # должно появиться wlan0 или wlpXsY 7. Если intf видим но не connect: дальше уже networkmanager/wpa_supplicant -- но это другой уровень. Главный урок: lsusb видит -- значит USB-стек работает; драйвер для конкретного чипа -- отдельная история. ID '0bda:b812' (Realtek 8812BU) поищите в Google -- сразу выходит инфа о нужном out-of-tree драйвере. На production-сервере лучше использовать аппаратуру с in-tree поддержкой (Intel AX200, AX210 -- работают сразу).

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. Что такое kernel module (.ko файл)?

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

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

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

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