Разработка модулей ядра linux

Записки программиста

Вопрос ковыряния ядра Linux впервые поднимался в этом блоге еще в далеком 2016-м году. Мы научились собирать ядро из исходников и цепляться к нему отладчиком. Но на этом все и заглохло. Тогда найти актуальную информацию по разработке ядерного кода в Linux, да еще и в удобоваримом виде, было проблемой. Я предпочел дождаться появления свежих книг по теме, а пока заняться изучением чего-то другого. И вот, спустя пять лет, такие книги были опубликованы. В связи с чем я решил попробовать написать пару модулей ядра, и посмотреть, как пойдет.

Проводить эксперименты было решено на Raspberry Pi 3 Model B+. На то есть три основные причины. Во-первых, малинка широко доступна и стоит недорого (особенно третья малинка, после выхода четвертой), что делает эксперименты повторяемыми. Во-вторых, запускать модули ядра на той же машине, где вы их разрабатываете, в любом случае не лучшая затея. Ведь ошибка в ядерном коде может приводить к какими угодно последствиям, не исключая повреждения ФС. И в-третьих, в отличие от виртуальной машины, малинка не отъедает ресурсы на вашей основной системе и позволяет реально взаимодействовать с реальным железом.

Образ системы был записан на SD-карту при помощи Raspberry Pi Imager. Приложение использовало образ на основе Raspbian 10 с ядром Linux 5.10. Это LTS-версия ядра, поддержка которого прекратится в декабре 2026-го года.

Для написания модулей ядра необходимо установить пакет с заголовочными файлами. В Raspbian это делается так:

В других системах пакет может называться linux-headers-* или как-то иначе.

Создадим новую директорию с файлом hello.c:

int init_module ( void ) {
pr_info ( «Hello world \n » ) ;
return 0 ;
}

void cleanup_module ( void ) {
pr_info ( «Goodbye world \n » ) ;
}

Читайте также:  Linux boots to grub menu

Рядом положим файл Makefile:

all :
$ ( MAKE ) — C / lib / modules /$ ( shell uname — r ) / build M =$ ( PWD ) modules

clean :
$ ( MAKE ) — C / lib / modules /$ ( shell uname — r ) / build M =$ ( PWD ) clean

Говорим make . В результате должен появиться файл hello.ko.

Теперь попробуем следующие команды:

# посмотреть информацию о модуле
modinfo hello.ko

# загрузить модуль
sudo insmod hello.ko

# список загруженных модулей ядра
lsmod | grep hello

# выгрузить модуль
sudo rmmod hello

# почитать логи
tail / var / log / syslog

При загрузке модуля будет вызвана процедура init_module() , а при выгрузке — cleanup_module() . Они напишут соответствующие логи через pr_info() , и мы увидим их в /var/log/syslog. С этим все понятно. Давайте перейдем к чему-то поинтересней.

Начнем с более детального рассмотрения pr_info() . Среди символов, экспортируемых ядром, вы его не найдете. Поиск по заголовочным файлам показывает, что на самом деле это макрос, объявленный в linux/printk.h:

А вот printk() уже является экспортируемым символом:

$ sudo cat /proc/kallsyms | grep ‘T printk’
.
801833e0 T printk_nmi_direct_exit
809f1508 T printk
809f16f4 T printk_deferred
.

Первая колонка — это адрес символа. Он отображаются, только если читать /proc/kallsyms под суперпользователем. В противном случае, мы увидим нули. Во второй колонке показано, откуда экспортируется символ. Согласно man nm , T и t соответствуют секции кода (.text). Заглавная буква означает, что символ виден глобально, а значит, может использоваться в модулях ядра. Теперь мы чуть лучше понимаем, как происходит общение между ядром и его модулями.

Далее, рассмотрим модуль посложнее:

MODULE_LICENSE ( «GPL» ) ;
MODULE_AUTHOR ( «Aleksander Alekseev» ) ;
MODULE_DESCRIPTION ( «A simple driver» ) ;

static char * name = «%username%» ;

module_param ( name , charp , 0 ) ;
MODULE_PARM_DESC ( name , «Enter your name» ) ;

static int __init init ( void ) {
pr_info ( «Hello, %s \n » , name ) ;
return 0 ;
}

Читайте также:  How to install linux mint on virtualbox

static void __exit cleanup ( void ) {
pr_info ( «Goodbye, %s \n » , name ) ;
}

module_init ( init ) ;
module_exit ( cleanup ) ;

Из этого примера мы узнаем ряд важных вещей. Во-первых, что процедуры, вызываемые при загрузке и выгрузке модуля, могут называться как угодно. Во-вторых, что в модуле можно указать не только его лицензию, но также автора и краткое описание. Сравните вывод modinfo для этого модуля и предыдущего. И в-третьих, модуль может принимать параметры:

В логах мы предсказуемо увидим:

Параметры, переданные модулю, видны через sysfs. Но чтобы это работало, код нужно немного изменить:

Если теперь пересобрать модуль, то можно сделать так:

Думаю, что для первого раза удивительных открытий достаточно. Полная версия кода доступна в этом репозитории на GitHub. Там же есть список дополнительных материалов для самостоятельного изучения.

Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.

Коротко о себе

Меня зовут Александр, позывной любительского радио R2AUK. Здесь я пишу об интересующих меня вещах и временами — просто о жизни.

Вы можете следить за обновлениями блога с помощью RSS, ВКонтакте, Telegram или Twitter. Также я являюсь одним из ведущих подкаста DevZen и иногда выкладываю видео на YouTube.

Мой e-mail — af is kon @gmail.com. Если вы хотите мне написать, прошу предварительно ознакомиться с этим FAQ.

Источник

Оцените статью
Adblock
detector