Наши партнеры








Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net

На главную -> MyLDP -> Тематический каталог -> Обновление и конфигурирование ядра

API ядра Linux, Часть 1: Вызов приложений, работающих в пользовательском пространстве, из ядра системы

Оригинал: "Kernel APIs, Part 1: Invoking user-space applications from the kernel"
Автор: M. Tim Jones
Дата публикации: 16 Feb 2010
Перевод: Н.Ромоданов
Дата перевода: 7 мая 2010 г.
Краткое содержание: Интерфейс системных вызовов Linux позволяет приложениям, работающим в пользовательском пространстве, обращаться к функциям ядра, но как насчет вызова этих приложений из ядра? Изучите интерфейс программирования прикладного программного обеспечения usermode-helper API и узнайте, как вызывать приложения пользовательского пространства и обрабатывать результат работы этих приложений.

Вызовы конкретных функций ядра (системные вызовы) являются естественной частью разработки приложений на GNU / Linux. Но что относительно другого направления, когда вызов осуществляется из пространства ядра в пользовательское пространство? Оказывается, что есть ряд приложений, использующих эту возможность, которыми вы, вероятно, пользуетесь каждый день. Например, когда ядро обнаруживает устройство, для которого необходимо загрузить модуль, то как происходит этот процесс? Происходит динамическая загрузка модуля, которая выполняется из ядра в рамках процесса, называемого usermode-helper.

Давайте начнем с изучения процесса usermode-helper, его интерфейса программирования (API), и некоторых примеров, в которых в ядре используется эта функция. Затем, используя API, создадим приложение-пример с тем, чтобы лучше понимать, как это работает и какие имеются ограничения.

Версия ядра

В настоящей статье изучается usermode-helper API для ядра 2.6.27.

Предупреждение

С помощью этого подхода обеспечивается функциональность, необходимая для вызова из ядра Linux приложений, работающих в пользовательском пространстве. Хотя такой подход допустим, вам следует детально рассмотреть, необходим ли такой вариант реализации. Это только один из вариантов, другие варианты подходят лучше.

Usermode-helper API

Интерфейс usermode-helper API представляет собой простое API с хорошо известным набором возможностей. Например, чтобы создать процесс из пользовательского пространства, вы обычно указываете имя исполняемого модуля и набор переменных среды (смотрите справочную страницу man execve). То же самое происходит при создании процесса из ядра. Но, поскольку вы запускаете процесс из пространства ядра, есть ряд дополнительных возможностей.

В таблице 1 приведен базовый набор функций ядра, которые есть в usermode-helper API.

Таблица 1: Базовые функции в usermode-helper API

Функция APIОписание
call_usermodehelper_setupПодготовка обработчика handler для вызова функции пользовательского пространства
call_usermodehelper_setkeysУстановка сессионных ключей для helper
call_usermodehelper_setcleanupУстановка функции очистки cleanup для helper
call_usermodehelper_stdinpipeСоздание конвейера stdin для helper
call_usermodehelper_execВызов функции пользовательского пространства

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

Таблица 2: Сокращенные функции usermode-helper API

Функция APIОписание
call_usermodehelperОсуществляет вызов функции пользовательского пространства
call_usermodehelper_pipeОсуществляет вызов функции пользовательского пространства с использованием конвейера stdin
call_usermodehelper_keysОсуществляет вызов функции пользовательского пространства с использованием сессионных ключей

Давайте сначала пройдемся по базовым функциям, а затем изучим возможности, которые предлагают сокращенные функции. Базовое API в своей работе использует ссылку на обработчик (handler), который является структурой subprocess_info. В этой структуре (которую можно найти в ./kernel/kmod.c) собраны все элементы, необходимые для данного экземпляра usermode-helper. Ссылка на структуру возвращается в вызове call_usermodehelper_setup. Дальнейшее конфигурирование структуры (и последующих вызовов) осуществляется с помощью вызовов call_usermodehelper_setkeys (работа с учетными данными), call_usermodehelper_setcleanup и call_usermodehelper_stdinpipe. Наконец, после того, как конфигурирование будет завершено, вы можете с помощью функции call_usermodehelper_exec вызвать сконфигурированное приложение, работающее в пользовательском режиме.

Базовые функции предоставят вам максимальную возможность управления, причем функции helper выполнят большую часть работы за один вызов. Вызовы, использующие конвейеры (call_usermodehelper_stdinpipe и функция call_usermodehelper_pipe из helper), создают соответствующий конвейер для использования его helper-ом. В частности, создается конвейер pipe (файловая структура в ядре). Приложение пользовательского пространства читает данные из pipe, а со стороны ядра осуществляется запись в pipe. А что касается записи, то выдача дампа памяти является единственным приложением, которое может использовать конвейер совместно с usermode-helper. В этом приложении (do_coredump() в ./fs/exec.c ), выдача дампа памяти выполняет запись данных через конвейер из пространства ядра в пользовательское пространство.

Соотношение между этими функциями и sub_processinfo вместе с деталями структуры subprocess_info показано на рис. 1.

Элементы интерфейса usermode-helper API

Рис.1. Элементы интерфейса usermode-helper API

Сокращенные функции из Таблицы 2 внутри себя вызывают функцию call_usermodehelper_setup и функцию call_usermodehelper_exec. Последние два вызова, указанные в таблице 2, вызывают, соответственно, call_usermodehelper_setkeys и call_usermodehelper_stdinpipe. Исходный код для call_usermodehelper_pipe вы можете найти в файле ./kernel/kmod.c, а исходный код для call_usermodehelper и call_usermodhelper_keys — в файле ./include/linux/kmod.h.

Зачем вызывать из ядра приложения, работающие в пользовательском пространстве?

Давайте теперь рассмотрим, где в ядре полезно использовать интерфейс программирования прикладного программного обеспечения usermode-helper API. В Таблице 3 приведен не исчерпывающий список приложений, использующих этот интернфейс, а лишь указаны некоторые интересные случаи применения.

Таблица 3. Приложения, использующие usermode-helper API для вызовов из ядра

ApplicationSource location
Загрузка модулей ядра./kernel/kmod.c
Управление питаниемt./kernel/sys.c
Управление группами./kernel/cgroup.c
Генерация ключей./security/keys/request_key.c
Передача событий в ядро./lib/kobject_uevent.c

Одним из наиболее ожидаемых применений usermode-helper API является загрузка модулей из пространства ядра. В функции request_module инкапсулирована функциональность usermode-helper API и предоставлен простой интерфейс. В обычных случаях ядро идентифицирует устройство или необходимую службу и делает вызов request_module, который осуществляет загрузку модуля. В случае использования usermode-helper API модуль загружается в ядро через modprobe (в пользовательском пространстве приложение вызывается с помощью request_module).

Аналогичной ситуацией, в которой загружаются модули, является "горячее" подключение устройств (добавление или удаление устройств во время работы системы). Эта возможность реализована с помощью usermode-helper API, вызывающего утилиту /sbin/hotplug, исполняемую в пользовательском пространстве.

Интересным приложением, использующим usermode-helper API (через request_module), является textsearch API (./lib/textsearch.c). Это приложение обеспечивает в ядре реализацию настраиваемой инфраструктуры, предназначенной для текстового поиска. Это приложение использует usermode-helper API для динамической загрузки поисковых алгоритмов, реализованных в виде загружаемых модулей. В релизе ядра 2.6.30 поддерживается три алгоритма, в том число алгоритм Бойера-Мура (Boyer-Moor, смотрите в ./lib/ts_bm.c), нативного подхода с использованием автомата с конечным числом состояний (./lib/ts_fsm.c) и, наконец, алгоритм Кнута-Морриса-Пратта (Knuth-Morris-Pratt, смотрите в ./lib/ts_kmp.cc).

Интерфейс usermode-helper API также используется в Linux для упорядоченного завершения работы системы. Когда необходимо отключить питание системы, ядро вызывает в пользовательском пространстве команду /sbin/poweroff, которая выполняет соответствующие действия. В таблице 3 приведен список других приложений и указывается место, где можно найти их исходный код.

Внутренняя организация usermode-helper API

Исходный код API для usermode-helper API можно найти в файле kernel/kmod.c (где проиллюстрировано его первоочередное использование в качестве используемого в ядре загрузчика модулей в пространство ядра). Основной объем работы в этой реализации выполняет функция kernel_execve. Обратите внимание, что kernel_execve является функцией, которая используется для запуска процесса init при загрузке системы и не использует usermode-helper API.

Реализация usermode-helper API довольно проста и очевидна (см. рис 2). Работа usermode-helper начинается с вызова функции call_usermodehelper_exec (которая используется для вызова приложения, предназначенного для работы в пользовательском пространсте, взятого из предварительно сконфигурированной структуры subprocess_info). Эта функция имеет два аргумента: ссылку на структуру subprocess_info и аргумент перечисляемого типа (определяющего ждать или не ждать, когда процесс будет запущен, и ждать или не ждать, когда процесс полностью завершится). Затем структура subprocess_info (или, точнее, элемент work_struct этой структуры) ставится в очередь в рабочую структуру (khelper_wq), которая выполняет асинхронный вызов.

Внутренняя организация usermode-helper API

Рис.2. Внутренняя организация usermode-helper API

Когда элемент помещается в khelper_wq, вызывается функция обработчика для очереди работ (в данном случае, __call_usermodehelper), которая исполняется внутри потока khelper. Эта функция начинается с удаления из очереди структуры subprocess_info, в которой содержится вся информация, необходимая для вызова из пользовательского пространства. Далее весь процесс зависит от значения переменной wait, имеющий перечисляемый тип. Если запрашиваемый процесс хочет ждать, пока весь данный процесс закончится, в том числе и вызовы, сделанные в пользовательском пространстве (UMH_WAIT_PROC) или вообще ничего не захочет ждать (UMH_NO_WAIT), то поток ядра создается из функции wait_for_helper. В противном случае, запрашиваемый процесс просто будет ждать, пока закончится вызов приложения в пользовательском пространстве (UMH_WAIT_EXEC), но не завершение приложения. В этом случае поток ядра создается для ____call_usermodehelper().

В потоке wait_for_helper установлен обработчик сигнала SIGCHLD, а для ____call_usermodehelper создается другой поток ядра. Но в потоке wait_for_helper делается вызов sys_wait4, который дожидается окончания потока ядра ____call_usermodehelper (указывается сигналом SIGCHLD). Затем поток выполняет всю необходимую очистку (либо освобождая структуры для UMH_NO_WAIT, либо просто посылая уведомление о завершении в call_usermodehelper_exec()).

Функция ____call_usermodehelper является именно тем местом, где выполняется фактическая работа по запуску приложения в пользовательском пространстве. Эта функция начинается с разблокирования всех сигналов и настройки службы хранения ключей. Она также устанавливает стандартный конвейер stdin (если это требуется). Затем, после еще некоторой минимальной инициализации приложение пользовательского пространства запускается с помощью вызова функции kernel_execve (из kernel/syscall.c), который включает в себя заранее заданный список path, argv (в том числе указывающий название приложения пользовательского пространства), а также настройку среды окружения. Когда этот процесс завершится, произойдет выход из потока с помощью обращения к функции do_exit().

Этот процесс также используется при завершении Linux, что похоже на операцию с использованием семафоров. Когда вызывается функция call_usermodehelper_exec, делается объявление о завершении работы системы. После того, как структура subprocess_info помещается в khelper_wq, делается вызов функции wait_for_completion (использующую переменную completion, указывающую о завершении, в качестве своего единственного аргумента). Обратите внимание, что эта переменная, также хранится в структуре subprocess_info как поле complete. Когда дочерние потоки захотят обратиться к функции call_usermodehelper_exec, они вызовут метод ядра complete, возвращающий переменную completion в структуре subprocess_info. Этот вызов разблокирует функцию, так что она может быть продолжена. Вы можете узнать подробности реализации этого API в файле include/linux/completion.h.

Дальнейшую информацию о usermode-helper API можно найти в разделе "Ресурсы" оригинала статьи.

Приложение - пример

Теперь давайте взглянем на простой пример использования usermode-helper API. Сначала рассмотрим стандартное API, а затем узнаем, как с помощью helper-функций это сделать еще проще.

Для этой демонстрации мы разработаем простой загружаемый модуль ядра, который вызывает API. В листинге 1 представлены функции модуля - заготовки, определяющие функции входа в модуль и выхода из модуля. Эти две функции вызываются по modprobe и insmod для модуля (функция входа в модуль) и по rmmod для модуля (выход из модуля).

Листинг 1. Функции модуля - заготовки

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kmod.h>

MODULE_LICENSE( "GPL" );

static int __init mod_entry_func( void )
{
  return umh_test();
}


static void __exit mod_exit_func( void )
{
  return;
}

module_init( mod_entry_func );
module_exit( mod_exit_func );

Использование usermode-helper API приведено в листинге 2, который мы изучим детально. Функция начинается с объявления целого ряда необходимых переменных и структур. Начинаем со структуры subprocess_info, которая содержит всю информацию, необходимую для выполнения вызова приложения пользовательского пространства. Этот вызов инициализируется, когда вы вызываете функцию call_usermodehelper_setup. Далее, определяем ваш список аргументов, называемый argv. Этот список аналогичен списку argv, используемый в обычных программах на языке C, в нем указывается приложение (первый элемент массива) и список аргументов. Для того, чтобы указать конец списка, требуется завершающий элемент NULL. Обратите внимание, что неявно подразумевается использование переменной argc (счетчик аргументов), поскольку известна длина списка argv. В этом примере именем приложения будет /usr/bin/logger, а его аргументом — help!, за которым следует завершающий NULL. Следующей необходимой переменной является массив, описывающий среду окружения (envp). Этот массив имеет список параметров, которые определяют среду, в которой будет выполняться приложение пользовательского пространства. В этом примере, мы определяем несколько типичных параметров, которые задаются для шелл оболочки, а в конце ставим завершающий NULL.

Листинг 2. Простой тест usermode helper API

static int umh_test( void )
{
  struct subprocess_info *sub_info;
  char *argv[] = { "/usr/bin/logger", "help!", NULL };
  static char *envp[] = {
        "HOME=/",
        "TERM=linux",
        "PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };

  sub_info = call_usermodehelper_setup( argv[0], argv, envp, GFP_ATOMIC );
  if (sub_info == NULL) return -ENOMEM;

  return call_usermodehelper_exec( sub_info, UMH_WAIT_PROC );
}

Далее обращаемся к call_usermodehelper_setup, для того чтобы создать нашу инициализированную структуру subprocess_info. Обратите внимание, что вы используете ранее инициализированные переменные и четвертый параметр, который указывает маску GFP для инициализации памяти. Из функции setup делается вызов kzalloc (в результате выделяется и обнуляется память ядра). Для этой функции требуется либо флаг GFP_ATOMIC, либо флаг GFP_KERNEL (первый определяет, что вызов не должен "засыпать", а второй — что "засыпать" можно). После быстрой проверки вашей новой структуры (а именно, что она не NULL), переходим к вызову, который выполняется с помощью функции call_usermodehelper_exec. Эта функция использует вашу структуру subprocess_info, а по параметру перечисляемого типа определяется, следует ли ждать (описано внутри раздела). И, вот оно! После загрузки модуля, вы должны увидеть сообщение в файле /var/log/messages.

Вы можете еще упростить этот процесс, если используете функцию call_usermodehelper, которая одновременно выполняет функцию call_usermodehelper_setup и функцию call_usermodehelper_exec. Как видно из листинга 3, в результате не только удаляется функция, но и пропадает необходимость управлять структурой subprocess_info.

Листинг 3. Еще более простой тест usermode-helper API

static int umh_test( void )
{
  char *argv[] = { "/usr/bin/logger", "help!", NULL };
  static char *envp[] = {
        "HOME=/",
        "TERM=linux",
        "PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };

  return call_usermodehelper( argv[0], argv, envp, UMH_WAIT_PROC );
}

Заметьте, что в листинге 3 выполнены все те же требования по настройке и осуществлению вызова (такие как инициализация массивов argv и envp). Единственная разница состоит в том, что функция helper выполняет функции setup и exec.

Двигаемся дальше

Интерфейс usermode-helper API является важной особенностью ядра, если учитывать его повсеместное и разностороннее применение (от загрузки модулей ядра, "горячего" подключения устройств и даже работу с событиями udev). Хотя важно проверять приложения на соответствие API, понятно, что это важная особенность и, следовательно, будет полезным дополнением к вашему инструментальным средствам ядра Linux.