Модуль ядра Linux — это объектный файл в формате ELF (расширение
Файл
После загрузки секции
Когда пользователь выполняет
После инициализации модуль функционирует как часть ядра: его код выполняется в контексте ядра, имеет полный доступ к памяти и аппаратным ресурсам. Модуль может регистрировать обработчики прерываний, создавать устройства в sysfs, выделять память через
При выгрузке:
Важно: если функция
Макросы
Модули собираются через kbuild — встроенную систему сборки ядра. Для внешнего (out-of-tree) модуля достаточно двух файлов.
Если модуль состоит из нескольких исходных файлов:
Сборка выполняется командой:
Параметр
Начиная с Linux 6.13 доступна альтернатива с флагом
По умолчанию внешние модули устанавливаются в
Модуль может использовать функции и переменные, экспортированные ядром или другими модулями. Экспорт выполняется макросами:
При загрузке модуля ядро проверяет, что каждый неопределённый символ (undefined symbol) из таблицы
Проверить, какие символы экспортирует загруженный модуль:
Или посмотреть в
При включённом
При загрузке модуля ядро сравнивает CRC из секции
Ограничение:
Утилита
Посмотреть зависимости конкретного модуля:
Модули могут принимать параметры при загрузке:
Передача при загрузке:
После загрузки параметры доступны через sysfs:
Если права доступа
Причина: модуль использует символ, который не экспортирован текущим ядром или не загружен модуль-зависимость. Проверка:
Решение: убедиться, что модуль-зависимость загружен, или пересобрать модуль против текущего ядра.
Каждый модуль содержит строку
Счётчик ссылок ненулевой. Проверка:
Третий столбец показывает
Частая причина — ошибка в
Проверить, включена ли проверка подписей:
Значение
Если
.ko), который загружается в адресное пространство ядра во время работы системы без перезагрузки. Модули позволяют добавлять драйверы, файловые системы, сетевые протоколы и отладочные механизмы «на лету», не пересобирая ядро целиком.Что представляет собой модуль на уровне формата
Файл
.ko — это обычный ELF-объект с дополнительными секциями, специфичными для ядра:| Секция | Назначение |
|---|---|
.modinfo | Метаданные модуля: автор, описание, зависимости, параметры, лицензия |
__ksymtab | Таблица экспортируемых символов |
__kcrctab | CRC-значения экспортируемых символов (при включённом CONFIG_MODVERSIONS) |
__versions | Имена и CRC импортируемых символов (при CONFIG_BASIC_MODVERSIONS) |
.init.text | Код, выполняемый только при инициализации модуля |
.exit.text | Код, выполняемый только при выгрузке модуля |
.gnu.linkonce.this_module | Структура struct module, через которую ядро управляет модулем |
После загрузки секции
.init.text освобождаются, что экономит память. Секция .exit.text остаётся в памяти, если модуль не поддерживает выгрузку (например, встроенный драйвер критичной подсистемы).Жизненный цикл модуля
Загрузка (insmod / modprobe)
Когда пользователь выполняет
insmod module.ko или modprobe module, происходит следующая последовательность:- Ядро читает ELF-заголовок и проверяет секции.
- Разрешаются зависимости: ядро ищет все символы, которые модуль импортирует, среди уже загруженных модулей и встроенного ядра.
- Если включён
CONFIG_MODVERSIONS, CRC каждого импортируемого символа сравнивается с CRC, записанным в__versionsмодуля. Несоответствие — отказ в загрузке.
- Выделяется память под код и данные модуля.
- Выполняются релокации.
- Вызывается функция инициализации, указанная макросом
module_init().
modprobe отличается от insmod тем, что автоматически подтягивает зависимости, читая файл /lib/modules/$(uname -r)/modules.dep, сгенерированный утилитой depmod.Работа
После инициализации модуль функционирует как часть ядра: его код выполняется в контексте ядра, имеет полный доступ к памяти и аппаратным ресурсам. Модуль может регистрировать обработчики прерываний, создавать устройства в sysfs, выделять память через
kmalloc/vmalloc, использовать workqueue и другие ядерные API.Выгрузка (rmmod / modprobe -r)
При выгрузке:
- Проверяется счётчик ссылок (
refcnt). Если другие модули или процессы используют данный модуль, выгрузка отклоняется.
- Вызывается функция очистки, указанная макросом
module_exit().
- Освобождается память, занятая модулем.
- Модуль удаляется из списка загруженных.
Важно: если функция
module_exit() не определена или модуль помечен как неудаляемый, выгрузка невозможна.Минимальный пример модуля
C:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
static int __init mymodule_init(void)
{
pr_info("mymodule: loaded\n");
return 0;
}
static void __exit mymodule_exit(void)
{
pr_info("mymodule: unloaded\n");
}
module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example");
MODULE_DESCRIPTION("Minimal kernel module");
Макросы
__init и __exit помещают функции в соответствующие секции, которые освобождаются после использования.Система сборки kbuild
Модули собираются через kbuild — встроенную систему сборки ядра. Для внешнего (out-of-tree) модуля достаточно двух файлов.
Kbuild-файл
Makefile:
obj-m := mymodule.o
Если модуль состоит из нескольких исходных файлов:
Makefile:
obj-m := mymodule.o
mymodule-y := core.o hardware.o protocol.o
Makefile-обёртка
Makefile:
KDIR ?= /lib/modules/$(shell uname -r)/build
default:
$(MAKE) -C $(KDIR) M=$$PWD
clean:
$(MAKE) -C $(KDIR) M=$$PWD clean
Сборка выполняется командой:
Bash:
make -C /lib/modules/$(uname -r)/build M=$PWD
Параметр
-C переключает make в каталог с исходниками ядра, а M=$PWD сообщает kbuild, что собирается внешний модуль. Результат — файл mymodule.ko в текущем каталоге.Начиная с Linux 6.13 доступна альтернатива с флагом
-f, которая избегает смены рабочего каталога:
Bash:
make -f /lib/modules/$(uname -r)/build/Makefile M=$PWD
Установка модуля
Bash:
make -C /lib/modules/$(uname -r)/build M=$PWD modules_install
По умолчанию внешние модули устанавливаются в
/lib/modules/$(uname -r)/updates/. Каталог можно изменить переменной INSTALL_MOD_DIR.Экспорт и импорт символов
Модуль может использовать функции и переменные, экспортированные ядром или другими модулями. Экспорт выполняется макросами:
| Макрос | Видимость |
|---|---|
EXPORT_SYMBOL(sym) | Все модули |
EXPORT_SYMBOL_GPL(sym) | Только модули с GPL-совместимой лицензией |
EXPORT_SYMBOL_NS(sym, ns) | Экспорт в именованное пространство символов |
При загрузке модуля ядро проверяет, что каждый неопределённый символ (undefined symbol) из таблицы
.symtab модуля разрешается через экспортированные символы. Если символ не найден — загрузка завершается ошибкой Unknown symbol.Проверить, какие символы экспортирует загруженный модуль:
Bash:
cat /proc/kallsyms | grep mymodule
Или посмотреть в
Module.symvers после сборки ядра.Версионирование модулей (CONFIG_MODVERSIONS)
При включённом
CONFIG_MODVERSIONS для каждого экспортируемого символа вычисляется CRC на основе полного прототипа. Файл Module.symvers хранит эти значения в формате:
Код:
<CRC> <Symbol> <Module> <Export Type> <Namespace>
0xe1cc2a05 usb_stor_suspend drivers/usb/storage/usb-storage EXPORT_SYMBOL_GPL USB_STORAGE
При загрузке модуля ядро сравнивает CRC из секции
__versions модуля с текущими значениями. Если прототип функции изменился между сборкой модуля и сборкой ядра, CRC не совпадёт и модуль не загрузится. Это защищает от бинарной несовместимости.Ограничение:
CONFIG_BASIC_MODVERSIONS поддерживает имена символов длиной до 64 байт. Для более длинных имён (например, в Rust-модулях) требуется CONFIG_EXTENDED_MODVERSIONS.Зависимости между модулями
Утилита
depmod сканирует все .ko-файлы в /lib/modules/$(uname -r)/ и строит граф зависимостей, записывая результат в modules.dep. Формат строки:
Код:
kernel/drivers/net/ethernet/intel/e1000/e1000.ko: kernel/lib/crc32.ko
modprobe читает этот файл и загружает зависимости в правильном порядке. Если зависимость отсутствует или несовместима по версии, загрузка прерывается.Посмотреть зависимости конкретного модуля:
Bash:
modinfo -F depends mymodule
Параметры модуля
Модули могут принимать параметры при загрузке:
C:
static int debug_level = 0;
module_param(debug_level, int, 0644);
MODULE_PARM_DESC(debug_level, "Verbosity level (0-3)");
Передача при загрузке:
Bash:
insmod mymodule.ko debug_level=2
modprobe mymodule debug_level=2
После загрузки параметры доступны через sysfs:
Bash:
cat /sys/module/mymodule/parameters/debug_level
Если права доступа
0644, значение можно изменить без перезагрузки модуля:
Bash:
echo 3 > /sys/module/mymodule/parameters/debug_level
Диагностика и типичные ошибки
Модуль не загружается: «Unknown symbol»
Причина: модуль использует символ, который не экспортирован текущим ядром или не загружен модуль-зависимость. Проверка:
Bash:
dmesg | tail -20
Решение: убедиться, что модуль-зависимость загружен, или пересобрать модуль против текущего ядра.
Несовпадение версий (version magic)
Каждый модуль содержит строку
vermagic, включающую версию ядра, конфигурационные флаги (SMP, preempt и др.). Если vermagic модуля не совпадает с текущим ядром, загрузка отклоняется. Проверка:
Bash:
modinfo mymodule.ko | grep vermagic
uname -r
Ошибка при выгрузке: «Module is in use»
Счётчик ссылок ненулевой. Проверка:
Bash:
lsmod | grep mymodule
Третий столбец показывает
refcnt. Если он больше нуля, нужно сначала выгрузить зависимые модули или дождаться завершения операций.Модуль загружается, но не работает
Частая причина — ошибка в
module_init(), которая возвращает ненулевой код. Ядро при этом выгружает модуль автоматически. Логи:
Bash:
dmesg | grep mymodule
journalctl -k --since "1 min ago"
Безопасность и ограничения
- Загрузка модуля требует
CAP_SYS_MODULE. В системах с включённым Secure Boot и подписанными модулями (CONFIG_MODULE_SIG_FORCE) неподписанный модуль не загрузится.
- Модуль выполняется в кольце 0 с полным доступом к памяти. Ошибка в коде модуля приводит к kernel panic.
- Для продакшена рекомендуется включать
CONFIG_MODULE_SIGи подписывать модули ключом, встроенным в ядро.
CONFIG_MODULE_SIG_FORCEполностью запрещает загрузку неподписанных модулей.
Проверить, включена ли проверка подписей:
Bash:
cat /proc/sys/kernel/modules_disabled
cat /sys/module/module/parameters/sig_enforce
Значение
sig_enforce = Y означает, что ядро принимает только подписанные модули.Быстрая проверка результата после сборки и загрузки
Bash:
## Сборка
make -C /lib/modules/$(uname -r)/build M=$PWD
## Загрузка
sudo insmod mymodule.ko
## Проверка
lsmod | grep mymodule
dmesg | tail -5
## Выгрузка
sudo rmmod mymodule
## Подтверждение выгрузки
dmesg | tail -3
Если
lsmod не показывает модуль после insmod, а dmesg содержит ошибку — модуль не прошёл инициализацию. Если rmmod возвращает «in use» — есть активные ссылки, и нужно найти зависимый модуль или процесс.
