06 сем Семинар 5 лаб 4.2 2 часть proc_write - chrislvt/OS GitHub Wiki
Три способа работы с ВФС proc. В ядре 5.20 функция proc_create_data нельзя передать указатель struct file_operations. Мы не можем передать в подсистему proc адреса которые определили эти. Надо посмотреть все в мануале про параметры proc_create и proc_create_data Защиту такого способоа это очень широко используемой способ регистрации функции в драйверах. Включили возможность регистрации функции используя struct file_operation...
Обратите внимание на функции show. Стоит return и возвращает результат seq_printf. Надо вызвать три раза функции show seq_printf.
В результате вызова proc create создается директория внутри которой файл. В примере рассматривается только read : нужно три функции open release read. В нашем примере только функцию open. Скорее всего если в функции read вносите свои действия и функциональности. Разработчики ядра построили свой слой который снижает сложность обмена данными "до чего то вроде printf". Если откроем текст функции printf то увидим что это большая функции. Выводит информацию в стандартный поток вывода, связан с монитором. На самом деле монитор это внешнее устройство, и работает с ним по принципу memory mapping, все пишется в видеобуфер. Но стремясь внести свою лепту в ядро предложили seq файл. Alloc в тексте присутствует seq_file.h. Написано много функции seq. Это является показателем того что ядро развивается. При этом разработчики не говорят почему они это сделали. Исходя из того что мы можем прочитать, стремление разработчкишв включить слой отказа устойчивости. При этом рассмотрим иллюстрацию.
Сама иллюстрация не несет никакой информации, в системе все буфферизуется. Вся инфа записывается в буфер. Базовая идея промежуточного слоя в том чтобы разработчик модуля записывал все данные в достаточно большую области памяти. На картинке видим что эта область память это буффер. Создается в оперативной памяти, человек подчеркивает RAM и подсистема single_file копирует данные пользователя. Relevant - востребованное данные, чаще всего обращаются. Дальнейшая обработка данных в частности передача пользователю proc файла осуществляется промежуточным уровнем. При этом пользователь может извлекать данные из буфера не сразу полностью все данные, а с помощью ряда вызовов read. Подсистема single_file является специальной формой файла последовательности. То есть seq файл. Причем если посмотреть на текст нашего примера, есть только proc_hello_open вызывает single_open а в нем передан адрес proc_hello_show, в результата передан указатель на seq_file и void * Вызывается seq_printf. Мы еще написали что фактически seq_printf используется вместо copy_to_user(). Мы видим single release и они нигде не вызываются. Значит вот эта подсистема single file выполняет. Таким образом эти функции могут использоваться без изменений и это видим в struct file_operations. Для того чтобы создать однофайловый экземпляр модуля, программbст вызовет функцию single_open которая вызовет подпрограмму которая обычно называется show. Эта подпрограмма получит адрес страницы памяти при этом важно отметить что функцию seq_printf в отличие от примера может быть вызвана произвольное число раз внутри функции show.
Особенности single файлов. Экземпляры сингл файлов не предназначены для больших файлов. 64 кб. Для большинство такого количество достаточно. Но функция seq printf контролирует перевыполнение при каждом вызове, то система автоматически увеличит буфер в памяти. Чаще всего создание такого же нового, такого же размера. После этого данные будут записаны. Это говорит о том, что данные потеряны не будут. Если увеличение буфера не возможно то seq_printf вернет отрицательный код ошибки. Подчеркивается что профессионал. программисты должны анализировать возвращаемые коды ошибок.
Обработка.
Подсистема не изменяет не интерпретирует получаемые данные. То есть ядро никак эти данные не анализирует. Фактически то что передадите в буфер то и выведете на экран. Такая же система как с обычным буфером и pipe. Если разработчику нужно проделать манипуляция с данными должны быть реализованы функции модуля ядра.
Допишем функцию proc_hello_write(). Важно не передавать больше данных чем может быть буферизовано между user space и kernel space. Функция min, которую мы будем использовать гарантирует это.
#include <asm/uaccess.h>
static char kernel_buffer[256];
static ssize_t proc_hello_write(struct file *inst, const char__user *buffer, size_t max_bytes_to_write, loff_t *offset)
{
ssize_t to_copy, not_copied;
to_copy = min(max_bytes_to_write, sizeof(kernel_buffer)); // здесь kernel buffer очевидно что он должен быть объявлен и размер должен быть
//установлен. После include это надо. Исходные коды - наша собственность мы пишем для себя, предметом продажи является .exe. Когда переменные
//вынесены в заголовок сразу видно что объявлено. Иначе затрудняет чтение.
not_copied = copy_from_user(kernel_buffer, buffer, to_copy);
// Если мы пишем в загружаемый модуль используя echo передадите "deutsch", получим "Hallo welt"
if (not_copied == 0)
{
printf("kernel buffer:\"%s"\"\n", kernel_buffer);
if (strcmp("deutsch", kernel_buffer,7) == 0)
{
out_str = "Hallo welt";
}
if (strcmp("english", kernel_buffer,7) == 0)
{
out_str = "Hello world";
}
}
return to_copy-not_copied;
}
// в file_operations добавить .write = proc_hello_write
// Еще право доступа добавить write. Это делается так S_IRUGO | S_IWUGO
Следующий пример связано с использованием copy_to_user copy_from_user. Вместо copy_to_user можно sprintf. В методичке пример убогий, ща его расширим. Здесь нет функции open И release, хоть они и вызываются неявно. По заданию кроме файла мы создаем симв. ссылку и директорию, в методе написано. По картинкам видно что страничное преобразование. След картинка, подчеркивается то что в ядре физические адреса, видим например з. модуля ядра, но не совсем так если мы попытаемся выгрузить использующий другой модуль, то сделать это не сможем. Это не преобразование, он получает физический адрес, адрес байта памяти с которого начинаются эти данные. В ядре и для того что система могла с ним работать есть таблица страниц. Адресное пространство делится на две части: резидентная и транзитная, но сегодня pagging и not pagging. Например таблица страниц процессов это системные таблицы. У нас отложенное связывание 4 уровня таблица для того что в памяти находились актуальные таблицы страниц. Это затратно, поскольку дополнительное обращение к памяти и т.д.
simple - неудачно приставка к функция, потому что в ядре много simple функции. Содержат мин набор действий. Могут быть переписаны. В примере не имеет никакого отношения.
#include <asm/uaccess.h>
static char msg[128];
static int len = 0;
static int len_check = 1;
static struct proc_dir_entry *p_fd;
int simple_proc_open(struct inode *sp_inode, struct file *sp_file)// во всех функциях ...
{
printk(KERN_INFO "called open \n");
return 0;
}
int simple_proc_release(struct inode *sp_inode, struc file *sp_file)
{
printk(KERN_INFO "called release \n");
return 0;
}
//Обратить внимание . Давайте на open в sturct file operations. int simple_proc_open(struct inode *sp_inode, struc file *sp_file) пишем с полным прототипом в struct file_operations. Но здесь используются другие имена, это наши функции но в соответствии с прототипом.
//Обратите внимание в read write только struct file
int simple_proc_read(struct file *sp_file, char __user *buf, size_t size, loff_t *offset)
{
if (len_check)
len_check = 0;
else
{
len_check = 1;
return 0;
}
printk(KERN_INFO "called read \n");
return len; // возвращает len потому что read может вызываться несколько раз.
}
int simple_proc_write(struct file *sp_file, char __user *buf, size_t size, loff_t *offset)
{
printk(KERN_INFO "called write \n");
len = size;
copy_from_user(msg, buf, len);
return len;
}
struct file_operations fops = {
.open = simple_proc_open,
.read = simple_proc_read,
.write = simple_proc_write,
.release = simple_proc_release,
}
static int __init_simpleproc(void)
{
printk(KERN_INFO "init simple proc \n");
p_fd = proc_create("simpleproc", 0666, NULL, &fops);
if (!p_fd)
{
printk(KERN_INFO "Error \n";
remove_proc_entry("simpleproc", NULL);
return 1;
}
return 0;
}
// серьезная ошибка. Нужно в exit вызывать remove_proc_entry для всех объектов (файл, директория, символическую ссылку)
// на самом деле struct file_operations содержит системные вызовы, которые зафиксированы в POSIX.
Если пишете просто read write как в методичке, доп инфы не увидим, но при этом когда вызывается read вызывается open.
Заголовок
- нотация через структуру как пример .read = ...
- через двоеточие