Записки программиста
Вопрос ковыряния ядра Linux впервые поднимался в этом блоге еще в далеком 2016-м году. Мы научились собирать ядро из исходников и цепляться к нему отладчиком. Но на этом все и заглохло. Тогда найти актуальную информацию по разработке ядерного кода в Linux, да еще и в удобоваримом виде, было проблемой. Я предпочел дождаться появления свежих книг по теме, а пока заняться изучением чего-то другого. И вот, спустя пять лет, такие книги были опубликованы. В связи с чем я решил попробовать написать пару модулей ядра, и посмотреть, как пойдет.
Проводить эксперименты было решено на Raspberry Pi 3 Model B+. На то есть три основные причины. Во-первых, малинка широко доступна и стоит недорого (особенно третья малинка, после выхода четвертой), что делает эксперименты повторяемыми. Во-вторых, запускать модули ядра на той же машине, где вы их разрабатываете, в любом случае не лучшая затея. Ведь ошибка в ядерном коде может приводить к какими угодно последствиям, не исключая повреждения ФС. И в-третьих, в отличие от виртуальной машины, малинка не отъедает ресурсы на вашей основной системе и позволяет реально взаимодействовать с реальным железом.
Образ системы был записан на SD-карту при помощи Raspberry Pi Imager. Приложение использовало образ на основе Raspbian 10 с ядром Linux 5.10. Это LTS-версия ядра, поддержка которого прекратится в декабре 2026-го года.
Для написания модулей ядра необходимо установить пакет с заголовочными файлами. В Raspbian это делается так:
sudo apt install raspberrypi-kernel-headers
ls / lib / modules / $ ( uname -r )
В других системах пакет может называться linux-headers-* или как-то иначе.
Создадим новую директорию с файлом hello.c:
int init_module ( void ) {
pr_info ( «Hello world \n » ) ;
return 0 ;
}
void cleanup_module ( void ) {
pr_info ( «Goodbye world \n » ) ;
}
Рядом положим файл Makefile:
obj — m += hello . o
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:
#define pr_info(fmt, . ) \
printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
А вот 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 ;
}
static void __exit cleanup ( void ) {
pr_info ( «Goodbye, %s \n » , name ) ;
}
module_init ( init ) ;
module_exit ( cleanup ) ;
Из этого примера мы узнаем ряд важных вещей. Во-первых, что процедуры, вызываемые при загрузке и выгрузке модуля, могут называться как угодно. Во-вторых, что в модуле можно указать не только его лицензию, но также автора и краткое описание. Сравните вывод modinfo для этого модуля и предыдущего. И в-третьих, модуль может принимать параметры:
sudo insmod param.ko name =Alex
sudo rmmod param
tail / var / log / syslog
В логах мы предсказуемо увидим:
Hello, Alex
Goodbye, Alex
Параметры, переданные модулю, видны через sysfs. Но чтобы это работало, код нужно немного изменить:
// module_param(name, charp, 0);
module_param ( name , charp , S_IRUGO ) ;
Если теперь пересобрать модуль, то можно сделать так:
cat / sys / module / param / parameters / name
Думаю, что для первого раза удивительных открытий достаточно. Полная версия кода доступна в этом репозитории на GitHub. Там же есть список дополнительных материалов для самостоятельного изучения.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.
Коротко о себе
Меня зовут Александр, позывной любительского радио R2AUK. Здесь я пишу об интересующих меня вещах и временами — просто о жизни.
Вы можете следить за обновлениями блога с помощью RSS, ВКонтакте, Telegram или Twitter. Также я являюсь одним из ведущих подкаста DevZen и выкладываю видео на YouTube.
Мой e-mail — af is kon @gmail.com. Если вы хотите мне написать, прошу предварительно ознакомиться с FAQ.
Форум русскоязычного сообщества Ubuntu
Страница сгенерирована за 0.036 секунд. Запросов: 23.
- Сайт
- Об Ubuntu
- Скачать Ubuntu
- Семейство Ubuntu
- Новости
- Форум
- Помощь
- Правила
- Документация
- Пользовательская документация
- Официальная документация
- Семейство Ubuntu
- Материалы для загрузки
- Совместимость с оборудованием
- RSS лента
- Сообщество
- Наши проекты
- Местные сообщества
- Перевод Ubuntu
- Тестирование
- RSS лента
© 2012 Ubuntu-ru — Русскоязычное сообщество Ubuntu Linux.
© 2012 Canonical Ltd. Ubuntu и Canonical являются зарегистрированными торговыми знаками Canonical Ltd.
[тупой вопрос] Зачем нужен linux-headers?
Если я компилирую ядро в домашней директории под юзером,из сырцов, скачанных из основных реп, то как добавить linux-headers, если он установлен в /usr/src/ ? Гуглил, но так и не понял, нужен ли linux-headers для компиляции. Если нужен, то для чего?
Serge ★
11.07.10 19:57:16 MSD

> Зачем нужен linux-headers?
Для сборки модулей ядра.
PolarFox ★★★★★
( 11.07.10 20:01:11 MSD )
Ответ на: комментарий от PolarFox 11.07.10 20:01:11 MSD
Дополнительных? Они же есть в linux-source.
Serge ★
( 11.07.10 20:02:22 MSD ) автор топика
Ответ на: комментарий от PolarFox 11.07.10 20:01:11 MSD
Модули могу удалять или вставлять в ядро. Про linux-headers ничего не сказано.
Serge ★
( 11.07.10 20:04:53 MSD ) автор топика
Ответ на: комментарий от Serge 11.07.10 20:02:22 MSD

А модуль для драйверов nvidia тоже в linux-source есть? Что-то не замечал.
ist76 ★★★★★
( 11.07.10 20:06:07 MSD )

а случайно ли не для сборки модулей nvidia, etc. для текущего ядра?
drakmail ★★★★
( 11.07.10 20:06:47 MSD )
Ответ на: комментарий от drakmail 11.07.10 20:06:47 MSD
Скорее всего. У меня ати и открытые драйвера. Никогда не ставил linux-headers
Serge ★
( 11.07.10 20:09:10 MSD ) автор топика

make -C /usr/src/linux-headers M=`pwd`
исходники не нужны в таком случае.
nanoo_linux ☆
( 11.07.10 20:21:13 MSD )

make headers_install
find usr -name .install -delete
sudo cp -r usr /
или я тебя не правильно понял?
ananas ★★★★★
( 11.07.10 21:00:08 MSD )

Компилять модули же.
GotF ★★★★★
( 11.07.10 21:03:05 MSD )

не понимаю сути проблемы-вопроса.. казалось бы, пишешь, что надо, используешь необходимые заголовочные файлы с определениями, обьявлениями.. если таковых не оказывается, то либо ищутся наиболее подходящие, либо пишутся собственные реализации..
а в чём, собственно, суть вопроса?
MiracleMan ★★★★★
( 11.07.10 22:08:42 MSD )
Для компиляции ядра в твоей схеме linux-headers не нужен, все идет в архиве ядра.
linux-headers нужен для компиляции программ/библиотек плотно работающих с ядром, точнее работающих в некоторых случаях с ядром не через glibc. Ну вот утилита mount например. Модули тут дело десятое.
kernel ★★☆
( 11.07.10 23:10:10 MSD )
Ответ на: комментарий от ananas 11.07.10 21:00:08 MSD

c scsi/ лучше это не делать, потом не соберутся ни sg_utils , ни писалки дисков, scsi/ предоставляется glibc
Sylvia ★★★★★
( 11.07.10 23:11:32 MSD )
Ответ на: комментарий от kernel 11.07.10 23:10:10 MSD

asm/
обычно используется гораздо чаще чем вы думаете )
Sylvia ★★★★★
( 11.07.10 23:46:48 MSD )
Ответ на: комментарий от Sylvia 11.07.10 23:46:48 MSD

как н странно, но, обычно, это просто ассоциируется с кривыми зависимостями..
MiracleMan ★★★★★
( 12.07.10 00:06:43 MSD )
Ответ на: комментарий от Sylvia 11.07.10 23:11:32 MSD

соберутся. достаточно в scsi.h в struct scsi_varlen_cdb_hdr изменить u8 на __u8
linux⚓︎
Файлы заголовков определяют способ определения функций в исходном файле. Они используются таким образом, чтобы компилятор мог проверить правильность использования функции в качестве сигнатуры функции (возвращаемое значение и параметры) в файле заголовка. Для этой задачи фактическая реализация функции не требуется.
Когда вы компилируете драйвер устройства как модуль ядра, вам необходимы установленные заголовочные файлы ядра. Также они требуются, если вы собираете пользовательское приложение, которое взаимодействует напрямую с ядром.
Версия заголовочных файлов должна соответствовать версии устанавливаемого ядра.
Сборка⚓︎
Обратите внимание
Данный пакет является частью архива с ядром Linux.
Убедитесь, что в архив не включены файлы которые могут помешать сборке.
make mrproper
Эта команда выполнит очистку дерева исходных текстов и удалит файлы конфигурации. Разработчики ядра рекомендуют, чтобы эта команда выполнялась перед каждым процессом компиляции.
Подготовьте заголовки для использования:⚓︎
make headers
Установка⚓︎
find usr/include -name '.*' -delete rm usr/include/Makefile cp -rv usr/include $LIN/usr
Заголовочные файлы, расположенные в системном каталоге /usr/include , должны всегда быть те, которые использовались при компиляции Glibc. Их никогда не следует заменять на чистые заголовочные файлы ядра или любые другие подготовленные заголовочные файлы.
Установленные файлы⚓︎
Данный пакет устанавливает множество заголовочных файлов, в частности /usr/include/asm/*.h, /usr/include/asm-generic/*.h, /usr/include/drm/*.h, /usr/include/linux/*.h, /usr/include/misc/*.h, /usr/include/mtd/*.h, /usr/include/rdma/*.h, /usr/include/scsi/*.h, /usr/include/sound/*.h, /usr/include/video/*.h, and /usr/include/xen/*.h