Прежде чем мы начнем Главу 2: Создание и запуск модулей, взгляните на краткое содержание Главы 1 здесь.
Поскольку вторая глава больше, я опубликую ее в нескольких частях.
Вторая глава посвящена практическому применению, знакомству с основными концепциями модулей и программирования ядра, а также мы создаем и запускаем модуль. Прежде чем продолжить, необходимо получить дерево исходного кода ядра из зеркальной сети kernel.org (приведенные здесь примеры должны работать с большинством ядер 2.6.x). Включено предупреждение с рекомендацией провести эксперименты с ядром в системе, данные которой вы можете позволить себе потерять.
Модуль Hello World
#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); static int hello_init(void) { printk(KERN_ALERT "Hello, world\n"); return 0; } static void hello_exit(void) { printk(KERN_ALERT "Goodbye, cruel world\n"); } module_init(hello_init); module_exit(hello_exit);
В приведенной выше программе под названием hello.c есть три специальных макроса ядра, указывающих роль этих функций:
module_init
указывает функцию, которая должна быть вызвана -hello_init
здесь, когда модуль загружен в ядро,module_exit()
указывает функцию, которая должна быть вызвана -hello_exit
здесь, когда модуль удален иMODULE_LICENSE()
, который сообщает ядру, что этот модуль имеет бесплатную лицензию, без которой ядро жалуется при загрузке модуля.
Кроме того, строка KERN_ALERT
является приоритетом сообщения.
Что делает printk
и зачем это нужно?
Функция printk
аналогична функции printf
и определена в ядре Linux. Это необходимо, потому что ядру нужна собственная функция печати, поскольку оно запускается само по себе, без библиотеки C при загрузке.
Как модуль может вызывать функцию ядра, например printk
?
Модуль связывается с ядром после того, как insmod
(поясняется ниже) его загрузил, и поэтому может получить доступ ко всем общедоступным переменным и функциям ядра.
Как мне собрать и запустить модуль?
Вы можете протестировать модуль с помощью утилит insmod
и rmmod
. Вы можете использовать lsmod
для вывода списка загруженных в данный момент модулей, хранящихся в proc/modules
.
Во-первых, вам нужно собрать модуль (при условии, что ваше дерево ядра настроено и построено в месте, где его может найти makefile), запустив make
:
%make
make[1]: Entering directory `/usr/src/linux-2.6.10' CC [M] /home/nj/src/misc-modules/hello.o Building modules, stage 2. MODPOST CC /home/nj/src/misc-modules/hello.mod.o LD [M] /home/nj/src/misc-modules/hello.ko make[1]: Leaving directory `/usr/src/linux-2.6.10' %su
root#insmod ./hello.ko
Hello, world root#rmmod hello
Goodbye cruel world root#
Обратите внимание, что только суперпользователь может загружать и выгружать модуль ядра.
Где мои результаты?
Приведенный выше вывод был взят из текстовой консоли. Если вы запускаете insmod
и rmmod
из эмулятора терминала, вы ничего не увидите на экране - выходные данные могут храниться в / var / log / messages.
Различия между модулями ядра и приложениями
- В то время как большинство приложений выполняют одну задачу от начала до конца, каждый модуль ядра просто регистрируется для обслуживания будущих запросов, и его инициализация немедленно прекращается. По сути, инициализация модуля заключается в подготовке к последующим вызовам модуля.
- Это означает, что все модули ядра событийно-управляемые, в то время как приложения могут или не могут.
- В то время как приложения при завершении могут избежать очистки, функция выхода модуля ядра ДОЛЖНА отменить все, что вызывала функция init, иначе ресурсы останутся до следующей загрузки.
- Приложения могут вызывать функции, которые не определяют - на этапе связывания разрешаются внешние ссылки на libc и т. Д. Но модуль связан только с ядром и может вызывать только функции, экспортируемые ядром.
- А поскольку никакая библиотека не связана с модулями, исходные файлы никогда не должны включать обычные файлы заголовков (‹stdarg.h› - одно из немногих исключений); соответствующие заголовки обычно находятся в include / linux и include / asm.
- Еще одно важное отличие - обработка ошибок: в то время как ошибка сегмента безвредна во время разработки приложения, ошибка ядра убивает процесс.
Пространство пользователя и пространство ядра
Модуль работает в пространстве ядра, а приложения - в пространстве пользователя.
Зачем нам это разделение и как оно осуществляется?
Операционная система должна поддерживать независимую работу программ и защиту от несанкционированного доступа к ресурсам. Это стало возможным благодаря тому, что ЦП обеспечивает защиту на разных рабочих уровнях.
Текущие процессоры имеют как минимум два уровня - уровни имеют разные роли, и программный код может переключаться между уровнями только через ограниченное количество шлюзов. Под Unix,
- ядро выполняется на самом высоком уровне (режим супервизора) - где все возможно, и
- приложения работают на самом низком уровне (пользовательский режим), где процессор регулирует прямой доступ к оборудованию и несанкционированный доступ к памяти.
Эти режимы выполнения могут иметь собственное отображение памяти и отдельное адресное пространство. Unix передает выполнение из пользовательского пространства в пространство ядра всякий раз, когда приложение выдает системный вызов или приостанавливается из-за аппаратного прерывания.
Что теперь обрабатывает код драйвера устройства?
Модули драйверов запускаются в пространстве ядра и расширяют функциональные возможности ядра. Код драйвера выполняет обе эти задачи, специфичные для устройства:
(i) Код ядра в модулях драйверов, выполняющих системный вызов, работает в контексте вызывающего процесса.
(ii) Код, обрабатывающий прерывания, является асинхронным по отношению к процессу.
Следите за новостями, чтобы узнать больше о главе 2 во второй части этого поста! Вы можете прочитать всю главу (Глава 2: Создание и запуск модулей) для более подробного понимания, не стесняйтесь обращаться ко мне в случае сомнений или ошибок.
Справочник: драйверы для устройств Linux, 3-е издание, книги O’Reilly