чтение-запись данных ядра через /proc

Вопросы программного кода и архитектуры Linux

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

чтение-запись данных ядра через /proc

Непрочитанное сообщение Olej » 12 мар 2012, 19:48

Эта тема переползла вот отсюда: viewtopic.php?f=3&t=1549&start=80#p3065 - +/- 5 дней того обсуждения влево-вправо ;-)

Речь идёт о программировании модулей ядра Linux и реализации в этих модулях неоднозначной операции read_proc_t ... а заодно и write_proc_t.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: чтение-запись данных ядра через /proc

Непрочитанное сообщение Olej » 12 мар 2012, 20:05

Olej писал(а):Речь идёт о программировании модулей ядра Linux и реализации в этих модулях неоднозначной операции read_proc_t ... а заодно и write_proc_t.
Сделал я такой маленький заготовец-архивчик, тестовая площадка для отработки вариантов:
variants.01.tgz
(5.21 КБ) 657 скачиваний
Здесь можно добавлять новые варианты read_proc_t, компилируя их как-то так:

Код: Выделить всё

$ make VARIANT=2
...
Там есть уже 4 варианта... для малых (<3Kb объёмов считываемых данных)
Достаточно любопытно ... и достаточно неожиданно:

1. то, что уже рассматривалось, и из-за прототы я не стал его менять:

Код: Выделить всё

static char msg_short[ LEN_MSG + 1 ] =
       ".........1.........2.........3.........4.........5.........6\n";

static ssize_t proc_node_read( char *buffer, char **start, off_t off,
                               int count, int *eof, void *data ) {
   int len = 0;
   char *buf_msg;
   LOG( "read: %d (buffer=%p, off=%ld, start=%p)", count, buffer, off, *start );
#if VARIANT == 0
   // тупо копируем весь буфер, пока return не станет <= off
   buf_msg = msg_short;
   len = strlen( buf_msg );
   strcpy( buffer, buf_msg );
   LOG( "copy bytes: %d", len );
#endif
   LOG( "return bytes: %d%s", len, *eof != 0 ? " ... EOF" : "" );
   return len;
};

Код: Выделить всё

[olej@notebook variants]$ ./insm
Module                  Size  Used by
mod_procr_3             1264  0
mod_procr_2             1224  0
mod_procr_1             1228  0
mod_procr_0             1192  0
fuse                   48375  2
/proc/mod_node_0  /proc/mod_node_1  /proc/mod_node_2  /proc/mod_node_3

[olej@notebook variants]$ ./mcat 40 mod_node_0
read + 40 bytes, input buffer: .........1.........2.........3.........4
read + 21 bytes, input buffer: .........1.........2.........3.........4.........5.........6
read + 00 bytes, input buffer: .........1.........2.........3.........4.........5.........6

[olej@notebook variants]$ dmesg | tail -n15 | grep !
! read: 40 (buffer=f4042000, off=0, start=(null))
! copy bytes: 61
! return bytes: 61
! read: 40 (buffer=f4042000, off=40, start=(null))
! copy bytes: 61
! return bytes: 61
! read: 19 (buffer=f4042000, off=61, start=(null))
! copy bytes: 61
! return bytes: 61
! read: 40 (buffer=f4042000, off=61, start=(null))
! copy bytes: 61
! return bytes: 61
Это отправная точка.
Здесь вообще никак не сигнализируется конец данных (конец файла) из кода реализации!
Решение о конце данных принимает скрытая реализация в ядре поддержки read_proc_t на основании того, что: возвращаемое значение (return) < (или <=?) чем переданное при вызове ранее значение off!
Совершенно дикая логика реализатора...

Остальные 3 варианта я нарисую чуть позже...

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: чтение-запись данных ядра через /proc

Непрочитанное сообщение Olej » 12 мар 2012, 21:02

Olej писал(а):Остальные 3 варианта я нарисую чуть позже...

Код: Выделить всё

static ssize_t proc_node_read( char *buffer, char **start, off_t off,
                               int count, int *eof, void *data ) {
   int len = 0;
   char *buf_msg;
   LOG( "read: %d (buffer=%p, off=%ld, start=%p)", count, buffer, off, *start );
#elif VARIANT == 1
   // ... на 1 шаг раньше ( return == off ) установлен eof
   buf_msg = msg_short;
   if( off < strlen( buf_msg ) ) {
      len = strlen( buf_msg );
      strcpy( buffer, buf_msg );
      LOG( "copy bytes: %d", len );
   }
   *eof = off + count >= strlen( buf_msg ) ? 1 : 0;
#endif
   LOG( "return bytes: %d%s", len, *eof != 0 ? " ... EOF" : "" );
   return len;
};
- вот таким образом мы можем сигнализировать EOF на 1 шаг чтения раньше.

Код: Выделить всё

[olej@notebook variants]$ ./mcat 40 mod_node_1
read + 40 bytes, input buffer: .........1.........2.........3.........4
read + 21 bytes, input buffer: .........1.........2.........3.........4.........5.........6
read + 00 bytes, input buffer: .........1.........2.........3.........4.........5.........6

[olej@notebook variants]$ dmesg | tail -n20 | grep !
! read: 40 (buffer=f1e6e000, off=0, start=(null))
! copy bytes: 61
! return bytes: 61
! read: 40 (buffer=f1e6e000, off=40, start=(null))
! copy bytes: 61
! return bytes: 61 ... EOF
! read: 40 (buffer=f1e6e000, off=61, start=(null))
! return bytes: 0 ... EOF

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: чтение-запись данных ядра через /proc

Непрочитанное сообщение Olej » 12 мар 2012, 21:07

Olej писал(а):Остальные 3 варианта я нарисую чуть позже...
Дальше - больше ;-)
Мы можем для всех последовательных чтений делать копирование всех данных всего 1 раз (это следствие того, что передача результата запроса в userspace делается скрыто ... и это очень плохо!):

Код: Выделить всё

   // 1-но копирование всего буфера на 1-м запросе
   buf_msg = msg_short;
   len = strlen( buf_msg );
   if( 0 == off ) {
      strcpy( buffer, buf_msg );
      LOG( "copy bytes: %d", len );
   }
   *eof = off + count >= strlen( buf_msg ) ? 1 : 0;

Код: Выделить всё

[olej@notebook variants]$ ./mcat 40 mod_node_2
read + 40 bytes, input buffer: .........1.........2.........3.........4
read + 21 bytes, input buffer: .........1.........2.........3.........4.........5.........6
read + 00 bytes, input buffer: .........1.........2.........3.........4.........5.........6

[olej@notebook variants]$ dmesg | tail -n7 | grep !
! read: 40 (buffer=f242b000, off=0, start=(null))
! copy bytes: 61
! return bytes: 61
! read: 40 (buffer=f242b000, off=40, start=(null))
! return bytes: 61 ... EOF
! read: 40 (buffer=f242b000, off=61, start=(null))
! return bytes: 61 ... EOF
- обратите внимание: сообщение о копировании здесь одно!

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: чтение-запись данных ядра через /proc

Непрочитанное сообщение Olej » 12 мар 2012, 21:10

Olej писал(а):
Olej писал(а):Остальные 3 варианта я нарисую чуть позже...
Дальше - больше ;-)
И наконец, мы можем копировать на каждом запросе ровно столько, сколько у нас запрашивали, но при этом в return возвращать ... "хитрое значение" ... превышающее off:

Код: Выделить всё

   // копирование только count байт на каждом запросе - аккуратно меняем return!
   buf_msg = msg_short;
   if( off >= strlen( buf_msg ) ) {
      *eof = 1;
   }
   else {
      int cp;
      len = strlen( buf_msg + off );
      cp = min( count, len );
      strncpy( buffer + off, buf_msg + off, cp );
      LOG( "copy bytes: %d", cp );
      len = off + cp;
      *eof = off + cp >= strlen( buf_msg );
   }

Код: Выделить всё

[olej@notebook variants]$ ./mcat 40 mod_node_3
read + 40 bytes, input buffer: .........1.........2.........3.........4
read + 21 bytes, input buffer: .........1.........2.........3.........4.........5.........6
read + 00 bytes, input buffer: .........1.........2.........3.........4.........5.........6

[olej@notebook variants]$ dmesg | tail -n8 | grep !
! read: 40 (buffer=f1e6e000, off=0, start=(null))
! copy bytes: 40
! return bytes: 40
! read: 40 (buffer=f1e6e000, off=40, start=(null))
! copy bytes: 21
! return bytes: 61 ... EOF
! read: 40 (buffer=f1e6e000, off=61, start=(null))
! return bytes: 0 ... EOF

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: чтение-запись данных ядра через /proc

Непрочитанное сообщение Olej » 12 мар 2012, 21:12

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

2. И это всё до тех пор, пока общий объм данных, подлежащих передаче, не превышает 3 Kb.
А дальше всё становится ещё хуже... :shock:

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: чтение-запись данных ядра через /proc

Непрочитанное сообщение Olej » 13 мар 2012, 00:07

Olej писал(а): 2. И это всё до тех пор, пока общий объм данных, подлежащих передаче, не превышает 3 Kb.
А дальше всё становится ещё хуже... :shock:
Начиная с того, что, наверное, уместно здесь процитировать (чтоб было под рукой) маловнятное описание из "Драйверы устройств Linux, Третья редакция" - http://dmilvdv.narod.ru/Translate/LDD3/ ... rying.html :
Когда процесс читает из вашего файла в /proc, ядро выделяет страницу памяти (то есть PAGE_SIZE байт), куда драйвер может записать данные для возврата в пространство пользователя. Этот буфер передаётся в вашу функцию, которая представляет собой метод, названный read_proc:

int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data);

Указатель page является буфером, куда вы будете записывать ваши данные; start используется функцией, чтобы сказать, где на странице были записаны интересующие данные (подробнее об этом позже); offset и count имеют такое же значение, как и для метода read. Аргумент eof указывает на целое число, которое должно быть установлено драйвером, чтобы сигнализировать, что у него нет больше данных для возвращения, в то время как data является указателем на специфические данные драйвера, которые можно использовать для внутренней бухгалтерии.

Эта функция должна возвращать количество байт данных, фактически размещённых в буфере page, так же как делает метод read для других файлов. Другие выходные значения - это *eof и *start. eof - это простой флаг, а вот использование значения start является несколько более сложным; его целью является помощь в реализации больших (более одной страницы) /proc файлов.

Параметр start имеет несколько нетрадиционное использование. Его целью является указать, где (в пределах страницы) находятся данные, которые будут возвращены пользователю. Когда вызывается метод proc_read, *start будет NULL. Если вы оставите его NULL, ядро предполагает, что данные были помещены на страницу, как если бы offset был 0; другими словами, оно предполагает простую версию proc_read, которая размещает всё содержимое виртуального файла на страницу, не обращая внимания на параметр смещения. Вместо этого, если вы установите *start в значение не-NULL, ядро предполагает, что данные, на которые указывает *start, принимают во внимание offset и готовы быть возвращены непосредственно к пользователю. В общем, простые методы proc_read, которые возвращают крошечные объёмы данных, просто игнорируют start. Более сложные методы устанавливают *start к page и размещают данные только начиная с запрошенного здесь смещения.

Давно существует другой важный вопрос с файлами /proc, которые также призван решить start. Иногда между последовательными вызовами read изменяется ASCII представление структур данных ядра, так что читающий процесс может обнаружить противоречивые данные от одного вызова к другому. Если *start установлен как небольшое целое значение, вызывающий использует его для увеличения filp->f_pos независимо от объёма возвращаемых данных, тем самым делая f_pos внутренним номером записи вашей процедуры read_proc. Если, например, ваша функция read_proc возвращает информацию из большого массива структур и пять из этих структур были возвращены в первом вызове, *start мог бы быть установлен в 5. Следующий вызов предоставляет то же значение, как offset; драйвер теперь знает, что надо начинать возвращать данные с шестой структуры в массиве. Это признается как "взлом" его авторами и это можно увидеть в fs/proc/generic.c.
Ну и кто здесь что понял? :-?

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: чтение-запись данных ядра через /proc

Непрочитанное сообщение Olej » 13 мар 2012, 01:07

Olej писал(а):1. Во всех вариантах главным решающим действием является правильная подгонка возвращаемого значения (>off, off+count) ... под то, что придумали разработчики read_proc_t !
И это признак того, насколько отвратительно оно реализовано.

2. И это всё до тех пор, пока общий объм данных, подлежащих передаче, не превышает 3 Kb.
А дальше всё становится ещё хуже... :shock:

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: чтение-запись данных ядра через /proc

Непрочитанное сообщение Olej » 13 мар 2012, 01:17

А я тем временем доделал write_proc_t, до которого раньше как-то руки не доходили.
Теперь это выглядит так:

Код: Выделить всё

static int parameter = 999;

static ssize_t proc_node_write( struct file *file, const char __user *buffer,
                                unsigned long count, void *data ) {
   char *buf_msg = msg_short;
   unsigned long len = min( count, LEN_MSG );
   LOG( "write: %ld (buffer=%p)", count, buffer );
   if( copy_from_user( buf_msg, buffer, len ) )
      return -EFAULT;
   *(buf_msg + len) = '\0';
   sscanf( buf_msg, "%d", &parameter );
   LOG( "parameter set to %d", parameter );
   return count;
}
ну, и в инициализации модуля:

Код: Выделить всё

   own_proc_node->read_proc = proc_node_read;
   own_proc_node->write_proc = proc_node_write;
всё, что касается parameter можно было и не писать - прямого отношения к записи в /proc это не имеет ... но писать в /proc чаще всего приходится, чтобы установить некоторое целочисленное значение в ядре, вот и хотелось проверить как? ведь это API ядра, здесь с atoi() не побалуешь, нет здесь такого ;-)

Код: Выделить всё

[olej@notebook variants]$ ./build
...
mod_proc_0.ko  mod_proc_1.ko  mod_proc_2.ko  mod_proc_3.ko
[olej@notebook variants]$ ./insm
Module                  Size  Used by
mod_proc_3              1514  0
mod_proc_2              1474  0
mod_proc_1              1486  0
mod_proc_0              1442  0
fuse                   48375  2
/proc/mod_node_0  /proc/mod_node_1  /proc/mod_node_2  /proc/mod_node_3
[olej@notebook variants]$ dmesg | tail -n100 | grep !
! /proc/mod_node_0 installed
! /proc/mod_node_1 installed
! /proc/mod_node_2 installed
! /proc/mod_node_3 installed

- это разные VARIANT (относительно read_proc_t).

Код: Выделить всё

[olej@notebook variants]$ cat /proc/mod_node_0
.........1.........2.........3.........4.........5.........6
[olej@notebook variants]$ echo 235135 > /proc/mod_node_0
[olej@notebook variants]$ cat /proc/mod_node_0
235135
[olej@notebook variants]$ dmesg | tail -n30 | grep !
! read: 3072 (buffer=f265b000, off=0, start=(null))
! copy bytes: 61
! return bytes: 61
! read: 3072 (buffer=f265b000, off=61, start=(null))
! copy bytes: 61
! return bytes: 61
! read: 3072 (buffer=f265b000, off=61, start=(null))
! copy bytes: 61
! return bytes: 61
! write: 7 (buffer=b789f000)
! parameter set to 235135
! read: 3072 (buffer=f265b000, off=0, start=(null))
! copy bytes: 7
! return bytes: 7
! read: 3072 (buffer=f265b000, off=7, start=(null))
! copy bytes: 7
! return bytes: 7
! read: 3072 (buffer=f265b000, off=7, start=(null))
! copy bytes: 7
! return bytes: 7
Вложения
variants.02.tgz
(3.78 КБ) 655 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: чтение-запись данных ядра через /proc

Непрочитанное сообщение Olej » 13 мар 2012, 12:27

Olej писал(а):2. И это всё до тех пор, пока общий объм данных, подлежащих передаче, не превышает 3 Kb.
А дальше всё становится ещё хуже... :shock:
Я подготовил ещё изменённый вариант для тестирования и такой возможности (см. архив).

1. теперь для читающего-пишущего модуля можно заказать буфер данных сколь угодно большого ;-) размера:

Код: Выделить всё

[olej@notebook variants]$ make VARIANT=1
...
VARIANT=1
[olej@notebook variants]$ sudo insmod mod_proc_0.ko size=1000000
[olej@notebook variants]$ dmesg | tail -n30 | grep !
! /proc/mod_node_0 installed, buffer size 1000000
- во! :lol: целых почти мегабайт...

2. туда можно гонять данные ... туда-сюда, хоть символьные, хоть бинарные...

Код: Выделить всё

[olej@notebook variants]$ cp -v mod_proc.c /proc/mod_node_0
`mod_proc.c' -> `/proc/mod_node_0'
[olej@notebook variants]$ cp -v /proc/mod_node_0 mod_proc.dubl
`/proc/mod_node_0' -> `mod_proc.dubl'
[olej@notebook variants]$ ls -l mod_proc.*
-rw-r--r-- 1 olej olej 1546 Мар 13 11:03 mod_proc.c
-rw-rw-r-- 1 olej olej 1546 Мар 13 11:05 mod_proc.dubl
-rw-r--r-- 1 olej olej  334 Мар 13 10:49 mod_proc.h
[olej@notebook variants]$ diff -a mod_proc.c mod_proc.dubl
[olej@notebook variants]$ cmp mod_proc.c mod_proc.dubl 
[olej@notebook variants]$ echo $?
0
- до 3Kb всё замечательно получается...
При больших данных всё становится гораздо более грустно :cry:

Код: Выделить всё

[olej@notebook variants]$ cp -v mod_proc_0.ko /proc/mod_node_0
`mod_proc_0.ko' -> `/proc/mod_node_0'
[olej@notebook variants]$ cp -v /proc/mod_node_0 mod_proc_0.kd
`/proc/mod_node_0' -> `mod_proc_0.kd'
[olej@notebook variants]$ ls -l mod_proc_0.*
-rw-rw-r-- 1 olej olej      2 Мар 13 11:09 mod_proc_0.kd
-rw-rw-r-- 1 olej olej 121262 Мар 13 10:49 mod_proc_0.ko
- лёгким движением руки ... 120 Kb превращаются ... превращаются ... превращаются в 2 байта :mrgreen:
Вложения
variants.03.tgz
(4.12 КБ) 631 скачивание

Ответить

Вернуться в «Linux изнутри»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 6 гостей