Прежде чем мы начнем Главу 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