Перейти к тексту

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

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

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

  • Закрыть [x]

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

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

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

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

  • Закрыть [x]

Нестандартные сценарии использования модулей ядра: Часть 45. Загрузка модуля ядра из программного кода

Олег Цилюрик, преподаватель тренингового отделения, Global Logic
Фото автора
Олег Иванович Цилюрик, много лет был разработчиком программного обеспечения в крупных центрах разработки: ВНИИ РТ, НПО "Дельта", КБ ПМ. Последние годы работал над проектами в области промышленной автоматики, IP телефонии и коммуникаций. Автор нескольких книг. Преподаватель тренингового отделения международной софтверной компании Global Logic.

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

Дата:  07.02.2013
Уровень сложности:  средний
Активность:  1190 просмотров
Комментарии:  


Введение

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


Динамическая загрузка модуля

Установка модулей на этапе разработки происходит с помощью команды insmod, а при переходе к эксплуатации модуля для его установки в систему используется команда modprobe. Симметрично, удаление установленных модулей выполняется командой rmmod. Но во всех этих случаях операции над модулями были статическими. Вопрос, рассматриваемый в данной статье, заключается в том, можно ли модули устанавливать (и выгружать) динамически (т.е. по требованию) из собственного программного кода? Подобная функциональность может оказаться востребованной в различных ситуациях, некоторые из которых перечислены ниже:

  • программы утилиты insmod, modprobe и rmmod - это программы из пользовательского пространства, поэтому интересно узнать, как они справляются с этой задачей;
  • в некоторых случаях от системы требуется функциональность, отсутствующая в ней и обеспечиваемая подгружаемым модулем ядра; в подобной ситуации было бы удобно подгрузить такой модуль непосредственно перед его использованием.

Классический пример такой ситуации - команда для монтирования файловой подсистемы (типа qnx4, minix и др.), которая по умолчанию не загружается системой. В этом случае модуль (minix.ko) подгружается по запросу из пользовательской утилиты mount, как показано ниже:

$ lsmod | grep minix 
$ sudo mount -t minix /dev/sda5 /mnt/sda5 
$ ls /mnt/sda5 
bin  boot  dev  etc  home  mnt  proc  root  sbin  tmp  usr  var 
$ lsmod | grep minix 
minix                  19212  1 

Разработчики определённого класса оборудования могли бы использовать родовой (generic) модуль драйвера, который при необходимости подгружал бы специализированный модуль драйвера для конкретной модели оборудования данного класса. Классическими примерами подобного подхода являются целые семейства драйверов (разграниченные по типам плат) в интерфейсах к платам класса E1/T1 в IP-телефонии, например, интерфейс DAHDI компании Digium или интерфейс компании Sangoma. В этом случае типовые модули динамически подгружаются из среды родового модуля ядра.

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


Загрузка модуля из процесса пользователя

Для загрузки модуля из пространства пользовательского процесса (как это делает insmod, modprobe и rmmod) существует системный вызов sys_init_module():
asmlinkage long sys_init_module( void __user *umod, 
unsigned long len, const char __user *uargs ); 

А для выгрузки модуля используется системный вызов sys_delete_module():

asmlinkage long sys_delete_module( const char __user *name, unsigned int flags );

К сожалению, процесс динамической загрузки и выгрузки модулей довольно слабо документирован, так как страницы справочника man и существующие Интернет-ресурсы содержат устаревшие сведения, относящиеся к реализациям на границе версий ядра 2.4 и 2.6. Поэтому для сбора информации нам придётся выполнить ряд практических экспериментов, чтобы собрать воедино фрагменты информации, разбросанные по исходным текста ядра Linux.

Для всех примеров, исходный код которых можно найти в архиве umaster.tgz в разделе "Материалы для скачивания", будет использоваться один и тот же тестовый модуль, который будет динамически загружаться в разных окружениях.


Листинг 1. Подгружаемый модуль (файл slave.ko).

#include "../common.c"

static char* parm1 = "";
module_param( parm1, charp, 0 );

static char* parm2 = ""; 
module_param( parm2, charp, 0 );

static char this_mod_file[ 40 ];

static int __init mod_init( void ) { 
   set_mod_name( this_mod_file, __FILE__ ); 
   printk( "+ module %s loaded: parm1=%s, parm2=%s\n", this_mod_file, parm1, parm2 );
   return 0; 
}

static void __exit mod_exit( void ) { 
   printk( "+ module %s unloaded\n", this_mod_file ); 
}

В данном модуле, как и во всех последующих примерах модулей, используется общий включаемый файл common.c (в родительском каталоге проекта):


Листинг 2. Общие определения (файл common.c).

#include <linux/module.h> 

MODULE_LICENSE( "GPL" ); 
MODULE_AUTHOR( "Oleg Tsiliuric <olej@front.ru>" ); 

static int __init mod_init( void ); 
static void __exit mod_exit( void ); 

module_init( mod_init ); 
module_exit( mod_exit ); 

inline void __init set_mod_name( char *res, char *path ) { 
   char *pb = strrchr( path, '/' ) + 1, 
        *pe = strrchr( path, '.' ); 
   strncpy( res, pb, pe - pb ); 
   sprintf( res + ( pe - pb ), "%s", ".ko" ); 
}; 

Проверим работу нашего модуля в автономном режиме путём стандартной загрузки с помощью команды insmod.

$ sudo insmod slave.ko 
$ dmesg | tail -n30 | grep + 
+ module slave.ko loaded: parm1=, parm2= 
$ sudo rmmod slave 
$ dmesg | tail -n30 | grep + 
+ module slave.ko unloaded 

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

$ sudo ./inst1 slave.ko parm1='строка1' parm2='строка2' 
загрузка модуля: slave.ko parm1=строка1 parm2=строка2 
размер файла модуля slave.ko = 94800 байт 
модуль slave.ko успешно загружен! 
$ dmesg | tail -n30 | grep + 
+ module slave.ko loaded: parm1=, parm2= 
$ lsmod | grep slave 
slave                   1009  0 

Примечание: если передать в модуль некорректные параметры или параметры в некорректном формате, то загрузка модуля выполнена не будет, а будет выведено сообщение об ошибке.

Для загрузки пробного модуля будет использоваться специальное приложение-загрузчик.


Листинг 3. Приложение для загрузки модулей (файл inst1.c).

#include <sys/stat.h> 
#include <errno.h> 
#include "common.h" 

// asmlinkage long sys_init_module     // системный вызов sys_init_module()
//   ( void __user *umod, unsigned long len, const char __user *uargs ); 

int main( int argc, char *argv[] ) { 
   char parms[ 80 ] = "", file[ 80 ] = SLAVE_FILE; 
   void *buff = NULL; 
   int fd, res; 
   off_t fsize;         /* общий размер в байтах */ 
   if( argc > 1 ) { 
      strcpy( file, argv[ 1 ] ); 
      if( argc > 2 ) { 
         int i; 
         for( i = 2; i < argc; i++ ) { 
            strcat( parms, argv[ i ] ); 
            strcat( parms, " " ); 
         } 
      } 
   } 
   printf( "загрузка модуля: %s %s\n", file, parms ); 
   fd = open( file, O_RDONLY ); 
   if( fd < 0 ) { 
      printf( "ошибка open: %m\n" ); 
      return errno; 
   } 
   {  struct stat fst; 
      if( fstat( fd, &fst ) < 0 ) { 
         printf( "ошибка stat: %m\n" ); 
         close( fd ); 
         return errno; 
      } 
      if( !S_ISREG( fst.st_mode ) ) { 
         printf( "ошибка: %s не файл\n", file ); 
         close( fd ); 
         return EXIT_FAILURE; 
      } 
      fsize = fst.st_size; 
   } 
   printf( "размер файла модуля %s = %ld байт\n", file, fsize ); 
   buff = malloc( fsize ); 
   if( NULL == buff ) { 
      printf( "ошибка malloc: %m\n" ); 
      close( fd ); 
      return errno; 
   } 
   if( fsize != read( fd, buff, fsize ) ) { 
      printf( "ошибка read: %m\n" ); 
      free( buff ); 
      close( fd ); 
      return errno; 
   } 
   close( fd ); 
   res = syscall( __NR_init_module, buff, fsize, parms ); 
   free( buff ); 
   if( res < 0 ) printf( "ошибка загрузки: %m\n" ); 
   else printf( "модуль %s успешно загружен!\n", file ); 
   return res; 
};

Этот пример (как и все приложения этой части изложения) включает файл ./common.h (внутри каталога umaster), в котором определяется имя загружаемого модуля — ./slave.ko, чтобы не вводить его каждый раз при запуске приложения из командной строки:

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <sys/syscall.h> 
#include <fcntl.h> 

#define SLAVE_FILE "./slave.ko"; 

Хотя тестовое приложение, приведённое в листинге 3, выглядит довольно сложным, но логика его работы крайне проста:

  1. выполняется поиск файла с откомпилированным модулем ядра с указанным именем (переменная file);
  2. определяется полный размер этого файла (переменная fsize);
  3. динамически выделяется буфер чтения buff указанного размера;
  4. в этот буфер читается образ загружаемого модуля (в формате объектного файла);
  5. системному вызову sys_init_module( void* umod, unsigned long len, const char* uargs ) передаются три параметра:
    • 1-й параметр – адрес образа модуля в памяти (buff);
    • 2-ой параметр — размер образа модуля (fsize);
    • в 3-ем параметре может передаваться строка параметров загрузки модуля, но если параметры загрузки не используются, то передается пустая строка, но ни в коем случае не NULL.

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


Листинг 4. Приложение для динамической выгрузки модулей (файл rem1.c).

#include "common.h"

//  системный вызов sys_delete_module() 
//  asmlinkage long sys_delete_module ( const char __user *name, unsigned int flags );
//  flags: O_TRUNC, O_NONBLOCK 

int main( int argc, char *argv[] ) { 
   char file[ 80 ]  = SLAVE_FILE; 
   int res; 

   if( argc > 1 ) strcpy( file, argv[ 1 ] ); 
   char *slave_mod = strrchr( file, '/' ) != NULL ? 
                     strrchr( file, '/' ) + 1 : 
                     file; 
   if( strrchr( file, '.' ) != NULL ) 
      *strrchr( file, '.' ) = '\0'; 
   printf( "выгружается модуль %s\n", slave_mod ); 
   res = syscall( __NR_delete_module, slave_mod, O_TRUNC ); 
   if( res < 0 ) printf( "ошибка выгрузки: %m\n" ); 
   else printf( "модуль %s успешно загружен!\n", slave_mod ); 
   return res; 
}; 

Проверим совместную работу всех компонентов путем динамической загрузки и последующей выгрузки модуля:

$ sudo ./inst1 slave.ko parm1='строка1' parm2='строка2' 
загрузка модуля: slave.ko parm1=строка1 parm2=строка2 
размер файла модуля slave.ko = 94800 байт 
модуль slave.ko успешно загружен! 
$ dmesg | tail -n30 | grep + 
+ module slave.ko loaded: parm1=строка1, parm2= строка2 
$ lsmod | grep slave 
slave                   1009  0 
$ sudo ./rem1 
выгружается модуль slave 
$ dmesg | tail -n30 | grep + 
+ module slave.ko unloaded
$ lsmod | head -n3 
Module                  Size  Used by 
fuse                   48375  2 
ip6table_filter         2227  0 

Если вам кажется не совсем корректным использование непрямых системных вызовов syscall(), то их можно заменить вызовами стандартной системной библиотеки для этих syscall(), изменив в каждом листинге всего по одной строке:

  • в файле inst2.c:
    ...
       res = init_module( buff, fsize, parms ); // вызов sys_init_module() 
    ...
    

  • в файле rem2.c:
    ...
       res = delete_module( slave_mod, O_TRUNC );  // flags: O_TRUNC, O_NONBLOCK
    ...
    

Почему я сразу не воспользовался вызовами init_module() и delete_module(), а вместо них использовал непрямые системные вызовы syscall()? Мне пришлось так поступить, так как ни в литературе, ни в справочных руководствах Linux, ни в Интернет мне не удалось найти примеров корректного использования init_module() и delete_module() или их точных прототипов. Напротив, все доступные источники содержат синтаксически некорректные и устаревшие примеры. Поэтому мне пришлось восстановить алгоритмы использования этих вызовов путём реинжиниринга через вызовы syscall().


Заключение

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



Загрузка

ИмяРазмерМетод загрузки
umaster.tgz4KBHTTP

Информация о методах загрузки


Ресурсы

Об авторе

Фото автора

Олег Иванович Цилюрик, много лет был разработчиком программного обеспечения в крупных центрах разработки: ВНИИ РТ, НПО "Дельта", КБ ПМ. Последние годы работал над проектами в области промышленной автоматики, IP телефонии и коммуникаций. Автор нескольких книг. Преподаватель тренингового отделения международной софтверной компании Global Logic.

Помощь по сообщениям о нарушениях

Сообщение о нарушениях

Спасибо. Эта запись была помечена для модератора.


Помощь по сообщениям о нарушениях

Сообщение о нарушениях

Сообщение о нарушении не было отправлено. Попробуйте, пожалуйста, позже.


developerWorks: вход


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


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

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

 


При первом входе в developerWorks для Вас будет создан профиль. Выберите информацию отображаемую в Вашем профиле — скрыть или отобразить поля можно в любой момент.

Выберите ваше отображаемое имя

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

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

(Должно содержать от 3 до 31 символа.)


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

 


Оценить эту статью

Комментарии

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