Linux - статьи


Файловая система /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> /* Необходимо для работы с файловой системой /proc */ #include <linux/proc_fs.h>


Пока мы знаем о двух способах получения информации от драйвера устройства: можно зарегистрировать драйвер и создать файл устройства, и создать файл в файловой системе /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 процесса, но в принципе возможна и иная организация принятия решения, например, разрешать ли одновременный доступ к файлу нескольким процессам и пр..




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



Причина, по которой для копирования данных используются функции 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> /* Необходимо для работы с файловой системой /proc */ #include <linux/proc_fs.h> /* определения функций get_user и put_user */ #include <asm/uaccess.h>

/* * Место хранения последнего принятого сообщения, * которое будет выводиться в файл, чтобы показать, что * модуль действительно может получать ввод от пользователя */ #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] = '\0'; /* Обычная строка, завершающаяся символом \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.


Содержание раздела