Содержание


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

Часть 36. Операции c файлами

Comments

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

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

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

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

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

Все предыдущие статьи, посвящённые разработке модулей ядра Linux, естественным образом разделялись на два больших цикла:

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

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

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

  • представление устройств в /dev и операции над ними;
  • управляющие интерфейсы /proc и /sys;
  • сетевые интерфейсы и протоколы.

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

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

  1. внутренние механизмы ядра, т.е. возможности и соответствующие им API ядра, которые может использовать код модуля (в основном это касается способов обслуживания аппаратных прерываний от устройств);
  2. обслуживание аппаратных шин в коде модуля, в первую очередь речь идёт о шинах PCI и устройствах USB.

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

Вспомогательные API ядра

Помимо множества вызовов API ядра, предназначенных для выполнения отчётливо ядерных функций, существует ограниченный ряд операций, которые помогают выполнять некоторые действия со стороны ядра, аналогично таким же операциям в пространстве пользователя. Именно поэтому в литературе и обсуждениях по ядру, о таких API говорят, как об общей группе под названием «хелперы» (или «хелперы пространства пользователя»). Большая часть подобных возможностей, обсуждаемых далее, крайне редко встречается в публикациях или упоминается в обсуждениях. Но они, кроме того, что могут оказаться крайне полезны в некоторых случаях, дают очень важное понимание протекания процессов в ядре, и того, что можно, а что нельзя сделать в рамках ядра.

Операции с файлами данных

Операции с данными в именованных файлах (регулярных файлах, FIFO, и т.д.) не относятся к тем возможностям, которыми программист активно пользуется внутри кода ядра (модуля). Но, во-первых, такие операции вполне возможны, а во-вторых, существует, как минимум, одна ситуация, когда такая возможность необходима: это чтение конфигурационных данных модуля при его запуске из конфигурационных файлов. Как будет показано, такую задачу не только можно выполнить из кода ядра, но даже существует несколько способов её решения. Рассмотрим подробный пример, приведённый в файле mod_file.c (этот файл можно найти в архиве file.tgz в разделе «Материалы для скачивания»).

Листинг 1. Чтение файла из модуля
#include <linux/module.h> 
#include <linux/fs.h> 
#include <linux/sched.h> 

static char* file = NULL; 
module_param( file, charp, 0 ); 

#define BUF_LEN 255 
#define DEFNAME "/etc/yumex.profiles.conf";  // произвольный текстовый файл 
static char buff[ BUF_LEN + 1 ] = DEFNAME; 

static int __init kread_init( void ) { 
    struct file *f; 
    size_t n; 
    if( file != NULL ) strcpy( buff, file ); 
    printk( "*** openning file: %s\n", buff ); 
    f = filp_open( buff, O_RDONLY, 0 ); 

    if( IS_ERR( f ) ) { 
        printk( "*** file open failed: %s\n", buff ); 
        return -ENOENT; 
    } 

    n = kernel_read( f, 0, buff, BUF_LEN ); 
    if( n ) { 
        printk( "*** read first %d bytes:\n", n ); 
        buff[ n ] = '\0'; 
        printk( "%s\n", buff ); 
    } else { 
        printk( "*** kernel_read failed\n" ); 
        return -EIO; 
    } 
    
    printk( "*** close file\n" ); 
    filp_close( f, NULL ); 
    return -EPERM; 
} 

module_init( kread_init );

Проверим работу нашего примера (используемый в тесте конфигурационный файл ./yumex.profiles.conf был подготовлен заранее в каталоге /etc и содержит несколько строк текста, а файл с именем ./yyy отсутствует, и указан в примере как недопустимое имя файла):

$ sudo insmod mod_file.ko file=/etc/yumex.profiles.conf
insmod: error inserting 'mod_file.ko': -1 Operation not permitted 
$ cat /etc/yumex.profiles.conf 
[main] 
lastprofile = yum-enabled$ 
$ dmesg | tail -n 40 
... 
**** openning file: /etc/yumex.profiles.conf 
**** read first 32 bytes: 
[main] 
lastprofile = yum-enabled 
*** close file

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

$ sudo insmod mod_file.ko file=./yyy 
insmod: error inserting 'mod_file1.ko': -1 Unknown symbol in module 
$ dmesg | tail -n20 | grep '^\*' 
*** openning file: ./yyy 
*** file open failed: ./yyy

В предыдущем примере использовался специальный вызов ядра kernel_read(), предназначенный только для этой цели. Но в следующем примере (файл mod_vfs.c) используется совершенно другой набор API ядра: вызовы имён, экспортируемых виртуальной файловой системой (VFS, вызовы вида vfs_*()).

Листинг 2. Чтение файла средствами виртуальной файловой системы
include <linux/module.h> 
#include <linux/fs.h> 
#include <linux/sched.h> 
#include <linux/uaccess.h> 

static char* file = NULL; 
module_param( file, charp, 0 ); 

#define BUF_LEN 255 
#define DEFNAME "/etc/yumex.profiles.conf"; 
static char buff[ BUF_LEN + 1 ] = DEFNAME; 

static int __init kread_init( void ) { 
    struct file *f; 
    size_t n; 
    long l; 
    loff_t file_offset = 0; 

    mm_segment_t fs = get_fs(); 
    set_fs( get_ds() ); 

    if( file != NULL ) strcpy( buff, file ); 
    printk( "*** openning file: %s\n", buff ); 
    f = filp_open( buff, O_RDONLY, 0 ); 

    if( IS_ERR( f ) ) { 
        printk( "*** file open failed: %s\n", buff ); 
        l = -ENOENT; 
        goto fail_oupen; 
    } 

    l = vfs_llseek( f, 0L, 2 ); // 2 means SEEK_END 
    if( l <= 0 ) { 
        printk( "*** failed to lseek %s\n", buff ); 
        l = -EINVAL; 
        goto failure; 
    } 
    printk( "*** file size = %d bytes\n", (int)l ); 

    vfs_llseek( f, 0L, 0 );    // 0 means SEEK_SET 
    if( ( n = vfs_read( f, buff, l, &file_offset ) ) != l ) { 
        printk( "*** failed to read\n" ); 
        l = -EIO; 
        goto failure; 
    } 
    buff[ n ] = '\0'; 
    printk( "%s\n", buff ); 
    printk( KERN_ALERT "**** close file\n" ); 
    l = -EPERM; 
failure: 
    filp_close( f, NULL ); 
fail_oupen: 
    set_fs( fs ); 
    return (int)l; 
} 

module_init( kread_init );

Хотя этот вариант и более сложный, но он обладает большей гибкостью, которая заключается в возможности использования всего набора функций файловых операций (таких, как показанная в примере vfs_llseek()), а не только узкого подмножества вызовов. А сложность состоит в том, что операции виртуальной системы «заточены» на работу с буферами в пространстве пользователя, и проверяют принадлежность адресов-параметров на принадлежность этому пространству. Для выполнения тех же операций с данными пространства ядра, нужно снять эту проверку на время операции и восстановить её после. Что и достигается использованием макровызовов: get_fs(), set_fs(), get_ds(). Но за этим надо тщательно следить, иначе операция завершится с кодом: Bad address. Проверим, как изменённый модуль может работать с тем же файлом данных:

$ sudo insmod mod_vfs.ko file=./xxx 
insmod: error inserting 'mod_vfs.ko': -1 Operation not permitted 
$ dmesg | tail -n30 | grep '^\*' 
*** openning file: ./xxx 
*** file size = 39 bytes 
*1 ......... 
*2 ......... 
**** close file

Возможность записи в файл из модуля ядра оказывается востребована ещё реже, чем чтение. Но и подобная операция, во-первых, возможна, а во-вторых, бывает полезна, например, для протоколирования событий, например, в целях отладки (возможности которой крайне сужены в случае с модулями ядра). Следующий пример из файла mdw.c демонстрирует такую возможность.

Листинг 3. Запись в файл из кода модуля
#include <linux/module.h>
#include <linux/fs.h> 
#include <linux/uaccess.h> 

static char* log = NULL; 
module_param( log, charp, 0 ); 

#define BUF_LEN 255 
#define DEFLOG "./module.log" 
#define TEXT "...............\n" 

static struct file *f; 

static int __init init( void ) { 
   ssize_t n = 0; 
   loff_t offset = 0; 
   mm_segment_t fs; 
   char buff[ BUF_LEN + 1 ] = DEFLOG; 
   if( log != NULL ) strcpy( buff, log ); 
   f = filp_open( buff, 
                  O_CREAT | O_RDWR | O_TRUNC, 
                  S_IRUSR | S_IWUSR ); 
   if( IS_ERR( f ) ) { 
      printk( "! file open failed: %s\n", buff ); 
      return -ENOENT; 
   } 
   printk( "! file open %s\n", buff ); 
   fs = get_fs(); 
   set_fs( get_ds() ); 
   strcpy( buff, TEXT ); 
   if( ( n = vfs_write( f, buff, strlen( buff ), &offset ) ) != strlen( buff ) ) { 
        printk( "! failed to write: %d\n", n ); 
        return -EIO; 
   } 
   printk( "! write %d bytes\n", n ); 
   printk( "! %s", buff ); 
   set_fs( fs ); 
   filp_close( f, NULL ); 
   printk( "! file close\n" ); 
   return -1; 
} 
module_init( init );

Ниже приведен пример запуска данного модуля:

$ sudo insmod mdw.ko 
insmod: error inserting 'mdw.ko': -1 Operation not permitted 
$ dmesg | tail -n40 | grep ! 
! file open ./module.log 
! write 16 bytes 
! ............... 
! file close 
$ ls -l *.log 
-rw------- 1 root root 16 Дек 31 21:23 module.log 
 $ sudo cat module.log 
...............

Обратите внимание, что файл module.log, в который ведётся запись, создаётся от имени root (сам модуль тоже можно исполнить командой insmod только от имени root), поэтому для последующей работы с таким файлом, возможно, придётся поменять его владельца.

Заключение

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

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


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


Похожие темы


Комментарии

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

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