Анатомия загружаемых модулей ядра Linux

Рассмотрение для ядра версии 2.6

Одним из важнейших новшеств в ядре Linux® стали загружаемые модули ядра (loadable kernel modules, LKM), появившиеся в версии 1.2. Они обеспечивают ядру гибкость и функциональность. В этой статье мы раскроем идеи, заложенные в эти модули, и узнаем, как эти независимые объекты динамически становятся частью ядра Linux.

М. Тим Джонс, инженер-консультант, Emulex

M. Тим Джонс (M. Tim Jones) является архитектором встраиваимого программного обеспечения и автором работ: Программирование Приложений под GNU/Linux, Программирование AI-приложений и Использование BSD-сокетов в различных языках программирования. Он имеет опыт разработки процессоров для геостационарных космических летательных аппаратов, а также разработки архитектуры встраиваемых систем и сетевых протоколов. Сейчас Тим работает инженером-консультантом в корпорации Эмулекс (Emulex Corp.) в г.Лонгмонт, Колорадо.



13.11.2008

Ядро Linux относится к категории так называемых монолитных – это означает, что большая часть функциональности операционной системы называется ядром и запускается в привилегированном режиме. Этот подход отличен от подхода микроядра, когда в режиме ядра выполняется только основная функциональность (взаимодействие между процессами [inter-process communication, IPC], диспетчеризация, базовый ввод-вывод [I/O], управление памятью), а остальная функциональность вытесняется за пределы привилегированной зоны (драйверы, сетевой стек, файловые системы). Можно было бы подумать, что ядро Linux очень статично, но на самом деле все как раз наоборот.

Ядро Linux динамически изменяемое – это означает, что вы можете загружать в ядро дополнительную функциональность, выгружать функции из ядра и даже добавлять новые модули, использующие другие модули ядра. Преимущество загружаемых модулей заключается в возможности сократить расход памяти для ядра, загружая только необходимые модули (это может оказаться важным для встроенных систем).

Linux – не единственное (и не первое) динамически изменяемое монолитное ядро. Загружаемые модули поддерживаются в BSD-системах, Sun Solaris, в ядрах более старых операционных систем, таких как OpenVMS, а также в других популярных ОС, таких как Microsoft® Windows® и Apple Mac OS X.

Анатомия модуля ядра

Загружаемые модули ядра имеют ряд фундаментальных отличий от элементов, интегрированных непосредственно в ядро, а также от обычных программ. Обычная программа содержит главную процедуру (main)в отличие от загружаемого модуля, содержащего функции входа и выхода (в версии 2.6 эти функции можно именовать как угодно). Функция входа вызывается, когда модуль загружается в ядро, а функция выхода – соответственно при выгрузке из ядра. Поскольку функции входа и выхода являются пользовательскими, для указания назначения этих функций используются макросы module_init и module_exit . Загружаемый модуль содержит также набор обязательных и дополнительных макросов. Они определяют тип лицензии, автора и описание модуля, а также другие параметры. Пример очень простого загружаемого модуля приведен на рисунке 1.

Рисунок 1. Код простого загружаемого модуля.
Код простого загружаемого модуля.

Версия 2.6 ядра Linux предоставляет новый, более простой метод создания загружаемых модулей. После того как модуль создан, можно использовать обычные пользовательские инструменты для управления модулями (несмотря на изменения внутреннего устройства): insmod (устанавливает модуль), rmmod (удаляет модуль), modprobe (контейнер для insmod и rmmod), depmod (для создания зависимостей между модулями) и modinfo (для поиска значений в модулях макроса). За дополнительной информацией о создании загружаемых модулей для ядра версии 2.6 обратитесь к ссылкам в разделе Ресурсы.


Анатомия объектного кода модуля ядра

Загружаемый модуль представляет собой просто специальный объектный файл в формате ELF (Executable and Linkable Format). Обычно объектные файлы обрабатываются компоновщиком, который разрешает символы и формирует исполняемый файл. Однако в связи с тем, что загружаемый модуль не может разрешить символы до загрузки в ядро, он остается ELF-объектом. Для работы с загружаемыми модулями можно использовать стандартные средства работы с объектными файлами (которые в версии 2.6 имеют суффикс .ko, от kernel object). Например, если вывести информацию о модуле утилитой objdump, вы обнаружите несколько привычных разделов, в том числе .text (инструкции), .data (инициализированные данные) и .bss (Block Started Symbol или неинициализированные данные).

В модуле также обнаружатся дополнительные разделы, ответственные за поддержку его динамического поведения. Раздел .init.text содержит код module_init, а раздел .exit.text – код module_exit code (рисунок 2). Раздел .modinfo содержит тексты макросов, указывающие тип лицензии, автора, описание и т. д.

Рисунок 2. Пример загружаемого модуля с разделами ELF
Пример загружаемого модуля с разделами ELF

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


Жизненный цикл загружаемого модуля ядра

Процесс загрузки модуля начинается в пользовательском пространстве с команды insmod (вставить модуль). Команда insmod определяет модуль для загрузки и выполняет системный вызов уровня пользователя init_module для начала процесса загрузки. Команда insmod для ядра версии 2.6 стала чрезвычайно простой (70 строк кода) за счет переноса части работы в ядро. Команда insmod не выполняет никаких действий по разрешению символов (вместе с командой kerneld), а просто копирует двоичный код модуля в ядро при помощи функции init_module; остальное делает само ядро.

Функция init_module работает на уровне системных вызовов и вызывает функцию ядра sys_init_module (рисунок 3). Это основная функция для загрузки модуля, обращающаяся к нескольким другим функциям для решения специальных задач. Аналогичным образом команда rmmod выполняет системный вызов функции delete_module, которая обращается в ядро с вызовом sys_delete_module для удаления модуля из ядра.

Рисунок 3. Основные команды и функции, участвующие в загрузке и выгрузке модуля
Основные команды и функции, участвующие в загрузке и выгрузке модуля

Во время загрузки и выгрузки модуля подсистема модулей поддерживает простой набор переменных состояния для обозначения статуса модуля. При загрузке модуля он имеет статус MODULE_STATE_COMING. Если модуль загружен и доступен, его статус – MODULE_STATE_LIVE. Если модуль выгружен – MODULE_STATE_GOING.


Подробности загрузки модуля

Теперь давайте взглянем на внутренние функции для загрузки модуля (рисунок 4). При вызове функции ядра sys_init_module сначала выполняется проверка того, имеет ли вызывающий соответствующие разрешения (при помощи функции capable). Затем вызывается функция load_module, которая выполняет механическую работу по размещению модуля в ядре и производит необходимые операции (я вскоре расскажу об этом). Функция load_module возвращает ссылку, которая указывает на только что загруженный модуль. Затем он вносится в двусвязный список всех модулей в системе, и все потоки, ожидающие изменения состояния модуля, уведомляются при помощи специального списка. В конце вызывается функция init() и статус модуля обновляется, чтобы указать, что он загружен и доступен.

Рисунок 4. Внутренний процесс загрузки модуля (упрощенно)
Внутренний процесс загрузки модуля (упрощенно)

Внутренние процессы загрузки модуля представляют собой анализ и управление модулями ELF. Функция load_module (которая находится в ./linux/kernel/module.c) начинает с выделения блока временной памяти для хранения всего модуля ELF. Затем модуль ELF считывается из пользовательского пространства во временную память при помощи copy_from_user. Являясь объектом ELF, этот файл имеет очень специфичную структуру, которая легко поддается анализу и проверке.

Следующим шагом является ряд "санитарных проверок" загруженного образа (является ли ELF-файл допустимым? соответствует ли он текущей архитектуре? и так далее). После того как проверка пройдена, образ ELF анализируется и создается набор вспомогательных переменных для заголовка каждого раздела, чтобы облегчить дальнейший доступ к ним. Поскольку базовый адрес объектного файла ELF равен 0 (до перемещения), эти переменные включают соответствующие смещения в блок временной памяти. Во время создания вспомогательных переменных также проверяются заголовки разделов ELF, чтобы убедиться, что загружаемый модуль корректен.

Дополнительные параметры модуля, если они есть, загружаются из пользовательского пространства в другой выделенный блок памяти ядра (шаг 4), и статус модуля обновляется, чтобы обозначить, что он загружен (MODULE_STATE_COMING). Если необходимы данные для процессоров (согласно результатам проверки заголовков разделов), для них выделяется отдельный блок.

В предыдущих шагах разделы модуля загружались в память ядра (временную), и было известно, какие из них используются постоянно, а какие могут быть удалены. На следующем шаге (7) для модуля в памяти выделяется окончательное расположение, и в него перемещаются необходимые разделы (обозначенные в заголовках SHF_ALLOC или расположенные в памяти во время выполнения). Затем производится дополнительное выделение памяти размера, необходимого для требуемых разделов модуля. Производится проход по всем разделам во временном блоке ELF,, и те из них, которые необходимы для выполнения, копируются в новый блок. Затем следуют некоторые служебные процедуры. Также происходит разрешение символов, как расположенных в ядре (включенных в образ ядра при компиляции), так и временных (экспортированных из других модулей).

Затем производится проход по оставшимся разделам и выполняются перемещения. Этот шаг зависит от архитектуры и соответственно основывается на вспомогательных функциях, определенных для данной архитектуры (./linux/arch/<arch>/kernel/module.c). В конце очищается кэш инструкций (поскольку использовались временные разделы .text), выполняется еще несколько служебных процедур (очистка памяти временного модуля, настройка sysfs) и, в итоге, модуль возвращает load_module.


Подробности выгрузки модуля

Выгрузка модуля фактически представляет собой зеркальное отражение процесса загрузки за исключением того, что для безопасного удаления модуля необходимо выполнить несколько "санитарных проверок". Выгрузка модуля начинается в пользовательском пространстве с выполнения команды rmmod (удалить модуль). Внутри команды rmmod выполняется системный вызов delete_module, который в конечном счете инициирует sys_delete_module внутри ядра (вернитесь к рисунку 3). Основные операции удаления модуля показаны на рисунке 5.

Рисунок 5. Внутренний процесс выгрузки модуля (упрощенно).
Внутренний процесс выгрузки модуля (упрощенно).

При вызове функции ядра sys_delete_module (с именем удаляемого модуля в качестве параметра) сначала выполняется проверка того, имеет ли вызывающий соответствующие разрешения. Затем по списку проверяются зависимости других модулей от данного модуля. При этом используется список modules_which_use_me, содержащий по элементу для каждого зависимого модуля. Если список пуст, т.е. зависимостей не обнаружено, то модуль становится кандидатом на удаление (иначе возвращается ошибка). Затем проверяется, загружен ли модуль. Ничто не запрещает пользователю запустить команду rmmod для модуля, который в данный момент устанавливается, поэтому данная процедура проверяет, активен ли модуль. После нескольких дополнительных служебных проверок предпоследним шагом вызывается функция выхода данного модуля (предоставляемая самим модулем). В заключение вызывается функция free_module.

К моменту вызова free_module уже известно, что модуль может быть безопасно удален. Зависимостей не обнаружено, и для данного модуля можно начать процесс очистки ядра. Этот процесс начинается с удаления модуля из различных списков, в которые он был помещен во время установки (sysfs, список модулей и т.д.). Потом инициируется команда очистки, зависящая от архитектуры (она расположена в ./linux/arch/<arch>/kernel/module.c). Затем обрабатываются зависимые модули, и данный модуль удаляется из их списков. В конце, когда с точки зрения ядра очистка завершена, освобождаются различные области памяти, выделенные для модуля, в том числе память для параметров, память для данных по процессорам и память модуля ELF (core и init).


Оптимизация ядра для управления модулями

Для многих приложений необходима возможность динамической загрузки модулей, но выгружать их не обязательно. Это позволяет ядру быть динамическим при запуске (загрузка модулей в зависимости от обнаруженных устройств), но неизменным во время работы. Если не требуется выгружать модули после загрузки, можно сделать несколько усовершенствований для сокращения количества кода, необходимого для управления модулями. В этом случае можно отключить параметр настройки ядра CONFIG_MODULE_UNLOAD, удалив тем самым значительную часть функциональности ядра, связанную с выгрузкой модулей.


Дальнейшее изучение

Это был лишь общий обзор процессов управления модулями в ядре. Лучшим источником дополнительной информации об управлении модулями является сам исходный код. Основные функции управления модулями содержатся в ./linux/kernel/module.c (и соответствующем файле заголовка ./linux/include/linux/module.h). Несколько функций, зависящих от архитектуры, находятся в ./linux/arch/<arch>/kernel/module.c. Наконец, функция автозагрузки ядра (которая автоматически загружает модуль из ядра при необходимости) находится в файле ./linux/kernel/kmod.c. Эта функция включается при помощи параметра настройки CONFIG_KMOD.

Ресурсы

Научиться

  • Anatomy of Linux loadable kernel modules (EN) - оригинал статьи.
  • Посетите блог Расти Рассела "Bleeding Edge", посвященный его текущим разработкам для ядра Linux. Расти – ведущий разработчик новой архитектуры модулей Linux.(EN)
  • Руководство по программированию модулей ядра Linux, хотя слегка устарело, содержит большое количество подробной информации о загружаемых модулях и их разработке. (EN)
  • В статье "Доступ к ядру Linux с помощью файловой системы /proc" (developerWorks, март 2006 г.) приведен подробный обзор программирования загружаемых модулей ядра с использованием файловой системы /proc.
  • Подробнее узнать о работе системы вызовов можно из статьи "Kernel command using Linux system calls" (EN) (developerWorks, март 2007 г.).
  • Чтобы узнать больше о ядре Linux, прочтите первую статью Тима "Анатомия ядра Linux" (developerWorks, июнь 2007 г.) из этой серии, в которой дан общий обзор ядра Linux и некоторых его интересных особенностей.
  • Прочитайте великолепное введение в формат ELF " Standards and specs: An unsung hero: the hardworking ELF" (EN) (developerWorks, декабрь 2005 г.). ELF является стандартным форматом объектных файлов для Linux. ELF - это гибкий формат файлов, охватывающий исполняемые образы, объектные файлы, общие библиотеки и даже дампы ядра. Более подробную информацию можно найти также в справочнике по формату (EN) (документ PDF) и в подробной книге по форматам ELF (EN).
  • На сайте Captain's Universe имеется великолепное введение в сборку загружаемых модулей с примерами make-файлов. Процесс сборки загружаемых модулей в ядре версии 2.6 был изменен (к лучшему).(EN)
  • Имеется несколько утилит для установки, удаления и управления модулями.(EN) Модули устанавливаются в ядро командой insmod , а удаляются командой rmmod . Для запроса о том, какие модули сейчас загружены в ядро, используйте команду lsmod . Поскольку модули могут зависеть друг от друга, существует команда depmod для создания файла зависимостей. Для автоматической загрузки зависимых модулей до загрузки основного модуля используйте команду modprobe (оболочка команды insmod). Наконец, прочесть информацию загружаемого модуля можно при помощи команды modinfo.
  • Статья "Linkers and Loaders" (EN) в журнале Linux Journal (ноябрь 2002 г.) содержит замечательное введение в работу редакторов связей и загрузчиков, использующих файлы ELF (включая разрешение символов и перемещение).
  • В разделе Linux сайта developerWorks имеются и другие ресурсы для Linux-разработчиков. Ознакомьтесь также с самыми популярными статьями и руководствами. (EN)
  • Ознакомьтесь с разделами советов и учебных пособий по Linux на developerWorks.
  • Раздел технических событий и Web-трансляций на developerWorks поможет вам оставаться в курсе новостей. (EN)

Получить продукты и технологии

  • Используйте ознакомительные версии ПО IBM, которые можно загрузить непосредственно с developerWorks, в вашем следующем проекте разработки для Linux. (EN)

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=351729
ArticleTitle=Анатомия загружаемых модулей ядра Linux
publish-date=11132008