MyTetra Share
Делитесь знаниями!
7. Файловая система /proc
Время создания: 20.09.2022 09:46
Текстовые метки: linux, ядро, модуль, программирование, язык, C, Си, пособие, документация
Раздел: Компьютер - Linux - Ядро - Пособие по программированию модулей ядра Linux
Запись: xintrea/mytetra_syncro/master/base/1663656380w9b2fojiff/text.html на raw.github.com

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

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

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

Обычные файловые системы располагаются на диске, а не просто в памяти (где находится /proc), и в этом случае номером индексного дескриптора (inode) является указатель на область диска, где располагается inode файла. Этот inode содержит информацию о файле, например разрешения, а также указатель на область или области диска, где находятся данные этого файла.

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

Вот простой пример, демонстрирующий использование файла /proc. Это Hello World для файловой системы /proc. Здесь у нас три части: создание файла /proc/helloworld в функции init_module, возвращение значения (и буфера) /proc/helloworld в функции обратного вызова procfile_read при считывании этого файла и его удаление в функции cleanup_module.

Указанный файл создается при загрузке модуля функцией proc_create. Возвращаемым значением здесь окажется struct proc_dir_entry, которое будет использовано для конфигурирования /proc/helloworld (например, указания владельца этого файла). Нулевое возвращаемое значение означает провал создания.

При каждом считывании /proc/helloworld вызывается функция procfile_read. У этой функции есть два важных параметра: буфер (второй параметр) и смещение (четвертый). Содержимое буфера будет возвращаться приложению, которое его считывает (например, команде cat). Смещение – это текущая позиция файла. Если возвращаемое значение функции не нулевое, тогда эта функция вызывается повторно. Так что будьте с ней внимательны – если она никогда не вернет нуль, то будет вызываться бесконечно.



$ cat /proc/helloworld

HelloWorld!



procfs1.c


/*

* procfs1.c

*/

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/proc_fs.h>

#include <linux/uaccess.h>

#include <linux/version.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)

#define HAVE_PROC_OPS

#endif

#define procfs_name "helloworld"

static struct proc_dir_entry *our_proc_file;

static ssize_t procfile_read(struct file *filePointer, char __user *buffer,

size_t buffer_length, loff_t *offset)

{

char s[13] = "HelloWorld!\n";

int len = sizeof(s);

ssize_t ret = len;

if (*offset >= len || copy_to_user(buffer, s, len)) {

pr_info("copy_to_user failed\n");

ret = 0;

} else {

pr_info("procfile read %s\n", filePointer->f_path.dentry->d_name.name);

*offset += len;

}

return ret;

}

#ifdef HAVE_PROC_OPS

static const struct proc_ops proc_file_fops = {

.proc_read = procfile_read,

};

#else

static const struct file_operations proc_file_fops = {

.read = procfile_read,

};

#endif

static int __init procfs1_init(void)

{

our_proc_file = proc_create(procfs_name, 0644, NULL, &proc_file_fops);

if (NULL == our_proc_file) {

proc_remove(our_proc_file);

pr_alert("Error:Could not initialize /proc/%s\n", procfs_name);

return -ENOMEM;

}

pr_info("/proc/%s created\n", procfs_name);

return 0;

}

static void __exit procfs1_exit(void)

{

proc_remove(our_proc_file);

pr_info("/proc/%s removed\n", procfs_name);

}

module_init(procfs1_init);

module_exit(procfs1_exit);

MODULE_LICENSE("GPL");

▍ 7.1 Структура proc_ops

В Linux v5.6+ структура proc_ops определена в include/linux/proc_fs.h. В более старых версиях она использовала file_operations для реализации в /proc пользовательских хуков. Однако в ней содержатся некоторые члены, которые в VFS не нужны, и всякий раз, когда VFS расширяет набор file_operations, код /proc раздувается. С другой стороны, этой структурой экономилось не только пространство, но и некоторые операции, что повышало ее быстродействие. Например, файл, который никогда не исчезает в /proc, может устанавливать proc_flag как PROC_ENTRY_PERMANENT, экономя в каждой последовательности открытия/чтения/закрытия 2 атомарных операции: 1 выделение памяти и 1 освобождение.

▍ 7.2 Считывание и запись файла /proc

Выше был описан очень простой пример использования /proc, в котором мы просто считывали файл /proc/helloworld. При этом в /proc также можно производить запись. Принцип тот же, что и в случае со считыванием – при записи в файл /proc вызывается соответствующая функция. Но здесь есть небольшое отличие – данные поступают от пользователя – значит их нужно импортировать из пользовательского пространства в пространство ядра (с помощью copy_from_user или get_user).

Причина использования copy_from_user либо get_user в том, что память Linux сегментирована (на некоторых процессорах с архитектурой Intel это может быть не так). То есть указатель сам по себе ссылается не на уникальную область в памяти, а на область в ее сегменте, и для использования этой памяти необходимо знать, что это за сегмент. Существует один сегмент памяти для ядра и по одному для каждого из процессов.

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

К этой памяти позволяют обращаться макросы put_user и get_user, но обрабатывают эти функции только один символ. Для обработки нескольких можно задействовать copy_to_user и copy_from_user. Поскольку буфер (в функции чтения или записи) находится в пространстве ядра, для функции записи данные необходимо импортировать, потому что они поступают из пространства пользователя. Функции чтения это не касается, так как в этом случае данные уже находятся в пространстве ядра.

procfs2.c


/*

* procfs2.c - создание "файла" в /proc

*/

#include <linux/kernel.h> /* Для работы с ядром. */

#include <linux/module.h> /* Для модулей. */

#include <linux/proc_fs.h> /* Для использования procfs.*/

#include <linux/uaccess.h> /* Для copy_from_user. */

#include <linux/version.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)

#define HAVE_PROC_OPS

#endif

#define PROCFS_MAX_SIZE 1024

#define PROCFS_NAME "buffer1k"

/* Эта структура содержит информацию о файле /proc. */

static struct proc_dir_entry *our_proc_file;

/* Этот буфер используется под хранение символа для данного модуля. */

static char procfs_buffer[PROCFS_MAX_SIZE];

/* Размер буфера. */

static unsigned long procfs_buffer_size = 0;

/* Эта функция вызывается при считывании файла /proc. */

static ssize_t procfile_read(struct file *filePointer, char __user *buffer,

size_t buffer_length, loff_t *offset)

{

char s[13] = "HelloWorld!\n";

int len = sizeof(s);

ssize_t ret = len;

if (*offset >= len || copy_to_user(buffer, s, len)) {

pr_info("copy_to_user failed\n");

ret = 0;

} else {

pr_info("procfile read %s\n", filePointer->f_path.dentry->d_name.name);

*offset += len;

}

return ret;

}

/* Эта функция вызывается при записи файла /proc. */

static ssize_t procfile_write(struct file *file, const char __user *buff,

size_t len, loff_t *off)

{

procfs_buffer_size = len;

if (procfs_buffer_size > PROCFS_MAX_SIZE)

procfs_buffer_size = PROCFS_MAX_SIZE;

if (copy_from_user(procfs_buffer, buff, procfs_buffer_size))

return -EFAULT;

procfs_buffer[procfs_buffer_size & (PROCFS_MAX_SIZE - 1)] = '\0';

pr_info("procfile write %s\n", procfs_buffer);

return procfs_buffer_size;

}

#ifdef HAVE_PROC_OPS

static const struct proc_ops proc_file_fops = {

.proc_read = procfile_read,

.proc_write = procfile_write,

};

#else

static const struct file_operations proc_file_fops = {

.read = procfile_read,

.write = procfile_write,

};

#endif

static int __init procfs2_init(void)

{

our_proc_file = proc_create(PROCFS_NAME, 0644, NULL, &proc_file_fops);

if (NULL == our_proc_file) {

proc_remove(our_proc_file);

pr_alert("Error:Could not initialize /proc/%s\n", PROCFS_NAME);

return -ENOMEM;

}

pr_info("/proc/%s created\n", PROCFS_NAME);

return 0;

}

static void __exit procfs2_exit(void)

{

proc_remove(our_proc_file);

pr_info("/proc/%s removed\n", PROCFS_NAME);

}

module_init(procfs2_init);

module_exit(procfs2_exit);

MODULE_LICENSE("GPL");

▍ 7.3 Управление файлом /proc с помощью стандартной файловой системы

Мы уже видели, как считывать и записывать файл в procfs с помощью интерфейса /proc. Но управлять такими файлами также можно и с помощью inode. Основная суть здесь в использовании продвинутых функций, таких как разрешения.

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

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

В /proc при каждой регистрации нового файла мы допустили указание, какая struct inode_operations будет использоваться для доступа к нему. Этот механизм мы и используем — struct inode_operations, которая включает указатель на struct proc_ops, которая, в свою очередь, включает указатели на наши функции procf_read и procfs_write.

Еще один интересный момент – это функция module_permission. Она вызывается всякий раз, когда процесс пытается сделать что-то с файлом /proc, и может решать, допускать его к этому файлу или нет. Сейчас она основана лишь на операции и uid текущего пользователя (в текущей ситуации это доступно из указателя на структуру, которая включает информацию о выполняющемся в данный момент процессе), но также может основываться и на чем-то другом, например на том, какие еще процессы работают с тем же файлом, на времени дня или последнем полученном вводе.

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

procfs3.c


/*

* procfs3.c

*/

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/proc_fs.h>

#include <linux/sched.h>

#include <linux/uaccess.h>

#include <linux/version.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)

#define HAVE_PROC_OPS

#endif

#define PROCFS_MAX_SIZE 2048

#define PROCFS_ENTRY_FILENAME "buffer2k"

static struct proc_dir_entry *our_proc_file;

static char procfs_buffer[PROCFS_MAX_SIZE];

static unsigned long procfs_buffer_size = 0;

static ssize_t procfs_read(struct file *filp, char __user *buffer,

size_t length, loff_t *offset)

{

static int finished = 0;

if (finished) {

pr_debug("procfs_read: END\n");

finished = 0;

return 0;

}

finished = 1;

if (copy_to_user(buffer, procfs_buffer, procfs_buffer_size))

return -EFAULT;

pr_debug("procfs_read: read %lu bytes\n", procfs_buffer_size);

return procfs_buffer_size;

}

static ssize_t procfs_write(struct file *file, const char __user *buffer,

size_t len, loff_t *off)

{

if (len > PROCFS_MAX_SIZE)

procfs_buffer_size = PROCFS_MAX_SIZE;

else

procfs_buffer_size = len;

if (copy_from_user(procfs_buffer, buffer, procfs_buffer_size))

return -EFAULT;

pr_debug("procfs_write: write %lu bytes\n", procfs_buffer_size);

return procfs_buffer_size;

}

static int procfs_open(struct inode *inode, struct file *file)

{

try_module_get(THIS_MODULE);

return 0;

}

static int procfs_close(struct inode *inode, struct file *file)

{

module_put(THIS_MODULE);

return 0;

}

#ifdef HAVE_PROC_OPS

static struct proc_ops file_ops_4_our_proc_file = {

.proc_read = procfs_read,

.proc_write = procfs_write,

.proc_open = procfs_open,

.proc_release = procfs_close,

};

#else

static const struct file_operations file_ops_4_our_proc_file = {

.read = procfs_read,

.write = procfs_write,

.open = procfs_open,

.release = procfs_close,

};

#endif

static int __init procfs3_init(void)

{

our_proc_file = proc_create(PROCFS_ENTRY_FILENAME, 0644, NULL,

&file_ops_4_our_proc_file);

if (our_proc_file == NULL) {

remove_proc_entry(PROCFS_ENTRY_FILENAME, NULL);

pr_debug("Error: Could not initialize /proc/%s\n",

PROCFS_ENTRY_FILENAME);

return -ENOMEM;

}

proc_set_size(our_proc_file, 80);

proc_set_user(our_proc_file, GLOBAL_ROOT_UID, GLOBAL_ROOT_GID);

pr_debug("/proc/%s created\n", PROCFS_ENTRY_FILENAME);

return 0;

}

static void __exit procfs3_exit(void)

{

remove_proc_entry(PROCFS_ENTRY_FILENAME, NULL);

pr_debug("/proc/%s removed\n", PROCFS_ENTRY_FILENAME);

}

module_init(procfs3_init);

module_exit(procfs3_exit);

MODULE_LICENSE("GPL");

Хотите больше примеров с procfs? Что ж, в первую очередь имейте ввиду, что по некоторой неофициальной информации procfs доживает свои дни, и нужно ориентироваться на использование sysfs. Поэтому, если хотите самостоятельно задокументировать что-то связанное с ядром, то подумайте о применении именно этого механизма.

▍ 7.4 Управление файлом /proc с помощью seq_file

Как мы видели, создание файла в /proc может вызывать сложности. Поэтому в качестве вспомогательного средства существует API seq_file, который помогает форматировать файл /proc для вывода. Основан этот API на выполнении последовательности из 3 функций: start(), next() и stop(). Запускает seq_file эту последовательность, когда пользователь считывает файл /proc.

Начинается все с вызова функции start() – если она вернет не NULL, то вызывается функция next(). Эта функция является итератором, перебирающим все данные. При каждом вызове next() также вызывается show(), которая записывает значения данных в буфер, считываемый пользователем. Функция next() вызывается до тех пор, пока не вернет NULL, после чего последовательность завершается, и вызывается функция stop().

Внимание! После окончания текущей последовательности начинается следующая. Это означает, что по завершению функции stop() снова вызывается start(). Заканчивается этот цикл, когда функция start() возвращает NULL. Общая схема описанного процесса показана на рис. 1.



Рис. 1: принцип работы seq_file


Интерфейс seq_file предоставляет базовые функции для proc_ops, такие как seq_read, seq_lseek и некоторые другие, но ничего для выполнения записи в файл /proc. Хотя вы по-прежнему можете использовать способ из предыдущего примера.

procfs4.c


/*

* procfs4.c - создание "файла" в /proc

* Эта программа задействует для управления файлом /proc библиотеку seq_file.

*/

#include <linux/kernel.h> /* Для работы с ядром. */

#include <linux/module.h> /* Для модулей. */

#include <linux/proc_fs.h> /* Для использования procfs */

#include <linux/seq_file.h> /* Для seq_file */

#include <linux/version.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)

#define HAVE_PROC_OPS

#endif

#define PROC_NAME "iter"

/* Эта функция вызывается в начале последовательности.

* То есть, когда:

* - первый раз считывается файл /proc

* - после завершения функции (в конце последовательности)

*/

static void *my_seq_start(struct seq_file *s, loff_t *pos)

{

static unsigned long counter = 0;

/* Начинаем новую последовательность? */

if (*pos == 0) {

/* Да => возвращается ненулевое значение для начала последовательности */

return &counter;

}

/* Нет => это конец последовательности, возвращается NULL для завершения считывания */

*pos = 0;

return NULL;

}

/* Эта функция вызывается после начала последовательности.

* Ее вызов повторяется до возвращения значения NULL (затем последовательность завершается).

*/

static void *my_seq_next(struct seq_file *s, void *v, loff_t *pos)

{

unsigned long *tmp_v = (unsigned long *)v;

(*tmp_v)++;

(*pos)++;

return NULL;

}

/* Эта функция вызывается в конце последовательности. */

static void my_seq_stop(struct seq_file *s, void *v)

{

/* Делать нечего, используем в start() статическое значение. */

}

/* Эта функция вызывается для каждого «шага» последовательности. */

static int my_seq_show(struct seq_file *s, void *v)

{

loff_t *spos = (loff_t *)v;

seq_printf(s, "%Ld\n", *spos);

return 0;

}

/* Эта структура формирует "функцию" для управления последовательностью. */

static struct seq_operations my_seq_ops = {

.start = my_seq_start,

.next = my_seq_next,

.stop = my_seq_stop,

.show = my_seq_show,

};

/* Эта функция вызывается при открытии файла /proc. */

static int my_open(struct inode *inode, struct file *file)

{

return seq_open(file, &my_seq_ops);

};

/* Эта структура формирует “функцию”, управляющую файлом /proc. */

#ifdef HAVE_PROC_OPS

static const struct proc_ops my_file_ops = {

.proc_open = my_open,

.proc_read = seq_read,

.proc_lseek = seq_lseek,

.proc_release = seq_release,

};

#else

static const struct file_operations my_file_ops = {

.open = my_open,

.read = seq_read,

.llseek = seq_lseek,

.release = seq_release,

};

#endif

static int __init procfs4_init(void)

{

struct proc_dir_entry *entry;

entry = proc_create(PROC_NAME, 0, NULL, &my_file_ops);

if (entry == NULL) {

remove_proc_entry(PROC_NAME, NULL);

pr_debug("Error: Could not initialize /proc/%s\n", PROC_NAME);

return -ENOMEM;

}

return 0;

}

static void __exit procfs4_exit(void)

{

remove_proc_entry(PROC_NAME, NULL);

pr_debug("/proc/%s removed\n", PROC_NAME);

}

module_init(procfs4_init);

module_exit(procfs4_exit);

MODULE_LICENSE("GPL");

Если вас интересует дополнительная информация, рекомендую заглянуть на эти страницы:

Также можете почитать код fs/seq_file.c в ядре.



 
MyTetra Share v.0.59
Яндекс индекс цитирования