Нестандартные сценарии использования модулей ядра

Часть 37. Создание новых процессов

Comments

Серия контента:

Этот контент является частью # из серии # статей: Нестандартные сценарии использования модулей ядра

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Нестандартные сценарии использования модулей ядра

Следите за выходом новых статей этой серии.

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

Запуск новых процессов из ядра

Как обычно, нам предстоит ответить на вопрос, который не очень часто встречается в публикациях и обсуждается на форумах. Мы должны установить, можно ли запустить новый пользовательский процесс из кода модуля? Интуитивный ответ очевиден сразу — да, так как все процессы, выполняющиеся в системе, запущены именно из кода ядра (одним из системных вызовов группы exec*()).

Новые процессы пользовательского пространства могут запускаться кодом ядра точно также, как они запускаются из пользовательского (POSIX) кода вызовами группы exec*(). Но хотя форма вызовов в обеих случаях и совпадает, по содержанию это действие (запуска процесса из ядра) имеет совершенно другой смысл. В пользовательском пространстве процессы создаются в два этапа. Сначала выполняется вызов fork() для создания нового адресного пространства, которое является абсолютным дубликатом порождающего процесса. Это адресное пространство позже становится пространством нового процесса, когда уже в нём производится вызов одной из функций семейства exec*(). Или, в редких случаях, при вызове типа fexecve(), когда источник кода нового процесса определяется не именем файла программы, а указанием дескриптора такого файла, но смысл в любом случае остается тем же. В пространстве ядра создание процесса происходит по-другому, так как здесь нельзя создать дубликат ядерного пространства (так как отсутствует эквивалент fork()). Для запуска нового процесса из кода ядра в API ядра присутствует специальный вызов call_usermodehelper().

В листинге 1 представлен простейший пример порождения новых процессов в системе по инициативе ядра (полный код примера можно найти в архиве exec.tgz в разделе "Материалы для скачивания").

Листинг 1. Запуск нового процесса из кода модуля ядра.
#include <linux/module.h> 

static char *str; 
module_param( str, charp, S_IRUGO ); 

Kit __Kit exec_Kit( Load ) { 
   Kit On; 
   char *argv[] = { "wall", "\nthis is wall message ", NULL }, 
        *envp[] = { NULL }, 
        msg[ 80 ]; 
   if( str ) { 
      sprintf( msg, "\n%s", str ); 
      argv[ 1 ] = msg; 
   } 
   rc = call_usermodehelper( "/usr/bin/wall", argv, envp, 0 ); 
   if( rc ) { 
      printk( KERN_INFO "failed to execute : %s\n", argv[ 0 ] ); 
      return rc; 
   } 
   printk( KERN_INFO "execute : %s %s\n", argv[ 0 ], argv[ 1 ] ); 
   msleep( 100 ); 
   return -1; 
} 

module_init( exec_init );

Вызов call_usermodehelper() получает параметры точно также, как и системный вызов пользовательского пространства execve() (через который выполняются все прочие прочие библиотечные вызовы семейства exec*()). C особенностями работы с execve() можно познакомиться в справочнике man.

$ man 2 execve

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

$ sudo insmod mod_exec.ko 

Broadcast message from root@notebook.localdomain (Mon Jan 30 18:13:10 2012): 
this is wall message 
insmod: error inserting 'mod_exec.ko': -1 Operation not permitted

Теперь посмотрим, что будет, если при загрузке модуля указать параметр (передаваемую строку).

$ sudo insmod mod_exec.ko str="new_string" 
  
Broadcast message from root@notebook.localdomain (Mon Jan 30 18:22:59 2012): 
new_string 
insmod: error inserting 'mod_exec.ko': -1 Operation not permitted 
$ dmesg | tail -n30 | grep -v ^audit 
execute : wall 
new_string

Как видно, модуль был успешно загружен, а затем был произведён запуск автономного пользовательского приложения, которое вывело широковещательное сообщение на все терминалы системы. Если указанное приложение не удаётся запустить, а это чаще всего происходит из-за неправильного указания полного путевого имени файла с запускаемой программой (в данном случае было указано /bin/wall — внесены изменения в код и выполнена компиляция), то код завершения загрузки модуля будет другим (ненулевым). Это важно, если учесть, что в ходе исполнения модулей ядра практически отсутствует вывод какой-либо отладочной информации.

$ sudo insmod mod_exec.ko 
insmod: error inserting 'mod_exec.ko': -1 Unknown symbol in module 
$ dmesg | tail -n30 | grep -v ^audit 
failed to execute : /bin/wall 
$ which wall 
/usr/bin/wall

Ограничение данного способа запуска приложений путём вызова call_usermodehelper() из кода ядра состоит в том, что процесс запускается без управляющего терминала и с нестандартным для него окружением! Для стандартных пользовательских приложений терминал и окружение первоначально создаются инициализирующим процессом init, и последующие процессы наследуют их от него (в последних версиях многих дистрибутивов эти операции производятся не init, а systemd). В этом можно легко убедиться, если в качестве пользовательского процесса использовать утилиту echo, а строки запуска соответствующим образом изменить. Полный код такого примера можно найти в файле mod_execho.c, который также расположен в архиве exec.tgz.

char *argv[] = { "/bin/echo", "this is wall message", NULL };
...
rc = call_usermodehelper( "/bin/echo", argv, envp, 0 );

Если запустить такой модуль, то мы получим такой вывод.

$ sudo insmod mod_execho.ko 
insmod: error inserting 'mod_execho.ko': -1 Operation not permitted 
$ dmesg | tail -n30 | grep -v ^audit 
execute : /bin/echo echo message

Здесь имеет место нормальное выполнение утилиты echo, однако мы не увидим результата её работы (вывода на терминал), поскольку этот управляющий терминал просто не существует (дескриптор 1, который в традиционном процессе открыт на SYSOUT в данном случае вообще не был открыт).

Тем не менее, такие фоновые процессы, инициируемые из модуля ядра (и из кода ядра) могут вполне успешно использоваться для выполнения самых разнообразных внутренних действий: отправка сигналов UNIX, посылка сетевых уведомлений, и т.д.

Вся основная работа по созданию и запуску процесса, как легко увидеть, сосредоточена в единственном вызове call_usermodehelper(), сигнатура которого приведена ниже, а полный код можно найти в файле <linux/kmod.h>.

static inline int call_usermodehelper( char *path, char **argv, 
char **envp, enum umh_wait wait ); 
enum umh_wait { 
   UMH_NO_WAIT = -1,   /* don't wait at all */ 
   UMH_WAIT_EXEC = 0,  /* wait for the exec, but not the process */ 
   UMH_WAIT_PROC = 1,  /* wait for the process to complete */ 
};

Приведенные константы имеют следующий смысл:

  • UMH_WAIT_PROC — ожидать завершения запущенного процесса;
  • UMH_WAIT_EXEC — ожидать завершения действий по запуску дочернего процесса, но не самого процесса;
  • UMH_NO_WAIT — не ожидать вообще ничего (в этом случае невозможно будет зафиксировать ошибку запуска процесса, например, вследствие указания ошибочного имени файла).

Заключение

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


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=852984
ArticleTitle=Нестандартные сценарии использования модулей ядра: Часть 37. Создание новых процессов
publish-date=12202012