Wiki

Глава 5. Файловая система /proc

5.1. Файловая система /proc: создание файлов, доступных для чтения

Linux предоставляет ядру и модулям ядра дополнительный механизм передачи информации заинтересованным в ней процессам -- это файловая система /proc. Первоначально она создавалась с целью получения сведений о процессах (отсюда такое название). Теперь она интенсивно используется и самим ядром, которому есть что сообщить! Например, /proc/modules -- список загруженных модулей, /proc/meminfo -- статистика использования памяти.

Методика работы с файловой системой /proc очень похожа на работу драйверов с файлами устройств: вы создаете структуру со всей необходимой информацией, включая указатели на функции-обработчики (в нашем случае имеется только один обработчик, который обслуживает чтение файла в /proc). Функция init_module регистрирует структуру, а cleanup_module отменяет регистрацию.

Основная причина, по которой используется proc_register_dynamic [6] состоит в том, что номер inode, для нашего файла, заранее неизвестен, поэтому мы даем возможность ядру определить его самостоятельно, чтобы предотвратить возможные конфликты. В обычных файловых системах, размещенных на диске, не в памяти, как /proc, inode указывает на то место в дисковом пространстве, где размещена индексная запись (index node, сокращенно -- inode) о файле. Inode содержит все необходимые сведения о файле, например права доступа, указатель на первый блок с содержимым файла.

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

Пример 5-1. procfs.c

/*
 *  procfs.c -  пример создания "файла" в /proc
 */
 
#include <linux/module.h> /* Необходимо для любого модуля */
#include <linux/kernel.h> /* Все-таки мы работаем с ядром! */
#include <linux/proc_fs.h>/* Необходимо для работы с файловой системой /proc */
 
struct proc_dir_entry *Our_Proc_File;
 
/* Обработчик чтения из файла в /proc.
 * 
 * Аргументы
 * =========
 * 1. Буфер с данными. Как его заполнить -- вы решаете сами
 * 2. Указатель на указатель на строку символов. 
 *    Если вы не желаете использовать буфер
 *    размещенный ядром.
 * 3. Текущая позиция в файле
 * 4. Размер буфера.
 * 5. Признак конца файла, "1" == EOF.
 * 6. Указатель на данные (необходим в случае единственного
 *    обработчика на несколько файлов в /proc)
 *
 * Порядок использования и возвращаемое значение
 * =============================================
 * Нулевое значение == "буфер пуст", т.е. "Конец файла". 
 * Отрицательное значение == код ошибки.
 *
 * Дополнительные сведения
 * =======================
 * Основные принципы реализации этой функции
 * я почерпнул не из документации, а из исходных текстов
 * модулей, выполняющих подобные действия. Меня интересовало использование
 * поля get_info в структуре proc_dir_entry (Если вам это интересно
 * то для поиска я пользовался утилитами find и grep),
 * Интересующий меня пример я нашел в <kernel source
 * directory>/fs/proc/array.c.
 *
 * Когда вам что-то непонятно, то лучше всего
 * поискать примеры в исходных текстах ядра. В этом состоит
 * огромное преимущество Linux перед другими ОС,
 * так как нам доступны все исходные тексты, так что -- 
 * пользуйтесь этим преимуществом!
 */
ssize_t
procfile_read(char *buffer,
              char **buffer_location,
              off_t offset, int buffer_length, int *eof, void *data)
{
  printk(KERN_INFO "inside /proc/test : procfile_read\n");
 
  int len = 0;          /* Фактическое число байт */
  static int count = 1;
 
  /* 
   * Мы всегда должны выдавать имеющуюся информацию,
   * если пользователь спрашивает -- мы должны ответить.
   *
   * Это очень важно, поскольку библиотечная функция read
   * будет продолжать обращаться к системному вызову
   * read до тех пор, пока ядро не ответит, что сведений больше нет
   * или пока буфер не будет заполнен.
   */
  if (offset > 0) {
    printk(KERN_INFO "offset %d : /proc/test : procfile_read, \
           wrote %d Bytes\n", (int)(offset), len);
    *eof = 1;
    return len;
  }
 
  /* 
   * Заполнить буфер и получить его размер
   */
  len = sprintf(buffer,
          "For the %d%s time, go away!\n", count,
          (count % 100 > 10 && count % 100 < 14) ? "th" :
          (count % 10 == 1) ? "st" :
          (count % 10 == 2) ? "nd" :
          (count % 10 == 3) ? "rd" : "th");
  count++;
 
 /* 
  * Вернуть размер буфера
  */
  printk(KERN_INFO
         "leaving /proc/test : procfile_read, wrote %d Bytes\n", len);
  return len;
}
 
int init_module()
{
  int rv = 0;
  Our_Proc_File = create_proc_entry("test", 0644, NULL);
  Our_Proc_File->read_proc = procfile_read;
  Our_Proc_File->owner = THIS_MODULE;
  Our_Proc_File->mode = S_IFREG | S_IRUGO;
  Our_Proc_File->uid = 0;
  Our_Proc_File->gid = 0;
  Our_Proc_File->size = 37;
 
  printk(KERN_INFO "Trying to create /proc/test:\n");
 
  if (Our_Proc_File == NULL) {
    rv = -ENOMEM;
    remove_proc_entry("test", &proc_root);
    printk(KERN_INFO "Error: Could not initialize /proc/test\n");
  } else {
    printk(KERN_INFO "Success!\n");
  }
 
  return rv;
}
 
void cleanup_module()
{
  remove_proc_entry("test", &proc_root);
  printk(KERN_INFO "/proc/test removed\n");
}
 

Пример 5-2. Makefile

obj-m += procfs.o

5.2. Файловая система /proc: создание файлов, доступных для записи

Пока мы знаем о двух способах получения информации от драйвера устройства: можно зарегистрировать драйвер и создать файл устройства, и создать файл в файловой системе /proc. Единственная проблема -- мы пока ничего не можем передать модулю ядра. Для начала попробуем организовать передачу данных модулю ядра посредством файловой системы /proc.

Поскольку файловая система /proc была написана, главным образом, для того чтобы получать данные от ядра, она не предусматривает специальных средств для записи данных в файлы. Структура proc_dir_entry не содержит указатель на функцию-обработчик записи. Поэтому, вместо того, чтобы писать в /proc напрямую, мы вынуждены будем использовать стандартный, для файловой системы, механизм.

Linux предусматривает возможность регистрации файловой системы. Так как каждая файловая система должна иметь собственные функции, для обработки inode и выполнять файловые операции, [7] то имеется специальная структура, которая хранит указатели на все необходимые функции-обработчики -- struct inode_operations, которая включает указатель на struct file_operations. Файловая система /proc, всякий раз, когда мы регистрируем новый файл, позволяет указать -- какая struct inode_operations будет использоваться для доступа к нему. В свою очередь, в этой структуре имеется указатель struct file_operations, а в ней уже находятся указатели на наши функции-обработчики.

Обратите внимание: стандартные понятия "чтение" и "запись", в ядре имеют противоположный смысл. Функции чтения используются для записи в файл, в то время как функции записи используются для чтения из файла. Причина в том, что понятия "чтение" и "запись" рассматриваются здесь с точки зрения пользователя: если процесс читает что-то из ядра -- ядро должно записать эти данные, если процесс пишет -- ядро должно прочитать то, что записано.

Еще один интересный момент -- функция module_permission. Она вызывается всякий раз, когда процесс пытается обратиться к файлу в файловой системе /proc, и принимает решение -- разрешить доступ к файлу или нет. На сегодняшний день, решение принимается только на основе выполняемой операции и UID процесса, но в принципе возможна и иная организация принятия решения, например, разрешать ли одновременный доступ к файлу нескольким процессам и пр..

Причина, по которой для копирования данных используются функции put_user и get_user, состоит в том, что процессы в Linux (по крайней мере в архитектуре Intel) исполняются в изолированных адресных пространствах, не пересекающихся с адресным пространством ядра. Это означает, что указатель, не содержит уникальный адрес физической памяти -- он хранит логический адрес в адресном пространстве процесса.

Единственное адресное пространство, доступное процессу -- это его собственное адресное пространство. Практически любой модуль ядра, должен иметь возможность обмена информацией с пользовательскими процессами. Однако, когда модуль ядра получает указатель на некий буфер, то адрес этого буфера находится в адресном пространстве процесса. Макрокоманды put_user и get_user позволяют обращаться к памяти процесса по указанному им адресу.

Пример 5-3. procfs.c

/* 
 *  procfs.c -  Пример создания файла в /proc, который доступен как на чтение, так и на запись.
 */
#include <linux/module.h>  /* Необходимо для любого модуля */
#include <linux/kernel.h>  /* Все-таки мы работаем с ядром! */
#include <linux/proc_fs.h> /* Необходимо для работы с файловой системой /proc */
#include <asm/uaccess.h>   /* определения функций get_user и put_user */
 
/* 
 * Место хранения последнего принятого сообщения,
 * которое будет выводиться в файл, чтобы показать, что
 * модуль действительно может получать ввод от пользователя
 */
#define MESSAGE_LENGTH 80
static char Message[MESSAGE_LENGTH];
static struct proc_dir_entry *Our_Proc_File;
 
#define PROC_ENTRY_FILENAME "rw_test"
 
static ssize_t module_output(struct file *filp, /* см. include/linux/fs.h   */
             char *buffer,      /* буфер с данными */
             size_t length,     /* размер буфера   */
             loff_t * offset)
{
  static int finished = 0;
  int i;
  char message[MESSAGE_LENGTH + 30];
 
  /* 
   * Для индикации признака конца файла возвращается 0.
   * Если этого не сделать, процесс будет продолжать 
   * пытаться читать из файла,
   * угодив в бесконечный цикл. 
   */
  if (finished) {
    finished = 0;
    return 0;
  }
 
  /* 
   * Для передачи данных из пространства ядра в пространство пользователя 
   * следует использовать put_user.
   * В обратном направлении -- get_user.
   */
  sprintf(message, "Last input:%s", Message);
  for (i = 0; i < length && message[i]; i++)
    put_user(message[i], buffer + i);
 
  /* 
   * Обратите внимание: в данной ситуации мы исходим из предположения,
   * что размер сообщения меньше, чем len, в противном случае сообщение будт обрезано.
   * В реальной ситуации, если длина сообщения больше чем 
   * len, то возвращается len, а остаток сообщения возвращается 
   * на последующих вызовах.
   */
  finished = 1;
 
  return i;             /* Вернуть количество "прочитанных" байт */
}
 
static ssize_t
module_input(struct file *filp, const char *buff, size_t len, loff_t * off)
{
  int i;
  /* 
   * Переместить данные, полученные от пользователя в буфер, 
   * который позднее будет выведен функцией module_output.
   */
  for (i = 0; i < MESSAGE_LENGTH - 1 && i < len; i++)
    get_user(Message[i], buff + i);
 
  Message[i] = &#39;\0&#39;;    /* Обычная строка, завершающаяся символом \0 */
  return i;
}
 
/* 
 * Эта функция принимает решение о праве на выполнение операций с файлом
 * 0 -- разрешено, ненулеое значение -- запрещено.
 *
 * Операции с файлом могут быть:
 * 0 - Исполнениеe (не имеет смысла в нашей ситуации)
 * 2 - Запись (передача от пользователя к модулю ядра)
 * 4 - Чтение (передача от модуля ядра к пользователю)
 *
 * Эта функция проверяет права доступа к файлу
 * Права, выводимые командой ls -l 
 * могут быть проигнорированы здесь.
 */
 
static int module_permission(struct inode *inode, int op, struct nameidata *foo)
{
  /* 
   * Позволим любому читать файл, но
   * писать -- только root-у (uid 0)
   */
  if (op == 4 || (op == 2 && current->euid == 0))
    return 0;
 
  /* 
   * Если что-то иное -- запретить доступ
   */
        return -EACCES;
}
 
/* 
 * Файл открыт -- пока нам нет нужды беспокоиться о чем-то
 * единственное, что нужно сделать -- это нарастить 
 * счетчик обращений к модулю.
 */
int module_open(struct inode *inode, struct file *file)
{
  try_module_get(THIS_MODULE);
  return 0;
}
 
/* 
 * Файл закрыт -- уменьшить счетчик обращений.
 */
int module_close(struct inode *inode, struct file *file)
{
  module_put(THIS_MODULE);
  return 0;             /* все нормально! */
}
 
static struct file_operations File_Ops_4_Our_Proc_File = {
        .read = module_output,
        .write = module_input,
        .open = module_open,
        .release = module_close,
};
 
/* 
 * Операции над индексной записью нашего файла. Необходима
 * для того, чтобы указать местоположение структуры
 * file_operations нашего файла, а так же, чтобы задать адрес
 * функции определения прав доступа к файлу. Здесь можно указать адреса
 * других функций-обработчиков, но нас они не интересуют.
 */
 
static struct inode_operations Inode_Ops_4_Our_Proc_File = {
  .permission = module_permission,      /* проверка прав доступа */
};
 
/* 
 * Начальная и конечная функции модуля
 */
int init_module()
{
  int rv = 0;
  Our_Proc_File = create_proc_entry(PROC_ENTRY_FILENAME, 0644, NULL);
  Our_Proc_File->owner = THIS_MODULE;
  Our_Proc_File->proc_iops = &Inode_Ops_4_Our_Proc_File;
  Our_Proc_File->proc_fops = &File_Ops_4_Our_Proc_File;
  Our_Proc_File->mode = S_IFREG | S_IRUGO | S_IWUSR;
  Our_Proc_File->uid = 0;
  Our_Proc_File->gid = 0;
  Our_Proc_File->size = 80;
 
  if (Our_Proc_File == NULL) {
    rv = -ENOMEM;
    remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root);
    printk(KERN_INFO "Error: Could not initialize /proc/test\n");
  }
 
  return rv;
}
 
void cleanup_module()
{
  remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root);
}
 
 

Хотите еще примеры работы с файловой системой /proc? Хорошо, но имейте ввиду, ходят слухи, что /proc уходит в небытие и вместо нее следует использовать sysfs. Дополнительные сведения о файловой системе /proc вы найдете в linux/Documentation/DocBook/. Дайте команду make help, она выведет инструкции по созданию документации в различных форматах, например: make htmldocs.