Как анализировать большой объем данных, переданных модулю ядра через файл/proc?

Изменить: Я нашел seq_file, который облегчает запись большого количества данных из ядра в пользовательское пространство. То, что я ищу, противоположное; API, который облегчает чтение большого количества данных (более одной страницы) из пользовательского пространства.

Изменить 2. Я реализую порт <stdio.h> в качестве модуля ядра, который сможет открыть /proc (а затем и другие виртуальные файловые системы), похожие на FILE и введите вход и выход, аналогичный <stdio.h>. Вы можете найти проект здесь.


Я нашел много вопросов о том, как ядро ​​может записывать большие объемы данных в /proc (для программ пользовательского пространства), но ничего для другого. Позвольте мне уточнить:

Этот вопрос в основном связан с алгоритмом, с помощью которого вводится токенизация (например, int или смесь int и строка и т.д.), , учитывая, что данные могут быть разбиты между несколькими буферами.

Например, представьте, что в модуль ядра отправляются следующие данные:

12345678 81234567 78123456 67812345 5678 1234 45678123 3456 7812 23456781

и ради этого примера, скажем, размер страницы, с помощью которой Linux подает обработчик /proc, составляет 20 байтов (против реального 4 КБ).

Функция, которая считывает данные из /proc (в модуле ядра), затем видит данные как таковые:

call 1:
"12345678 81234567 78"
call 2:
"123456 67812345 5678"
call 3:
" 1234 45678123 3456 "
call 4:
"7812 23456781"

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

Теперь я нашел seq_file, который, по-видимому, предназначен только тогда, когда ядро ​​хочет писать данные пользователю, а не читать (или это может быть что HOWTO ужасно написан).

Что я сделал

До сих пор я пришел со следующим решением (я пишу из памяти, поэтому я могу пропустить пару проверок ошибок, но голый со мной):

В фазе инициализации (скажем init_module):

initialize mutex1 to 1 and mutex2 to 0
create /proc entry
call data_processor

/proc reader:

1. down(mutex1)    /* down_interruptible of course, but let not get into details */

2. copy_from_user to an internal buffer
   buffer_index = 0
   data_length = whatever the size is

3. strip spaces from end of buffer (except if all left from buffer is 1 space)
   if so, there_was_space_after = 1 else 0

4. up(mutex2)

Я объясню, почему я убираю пробелы позже

get_int Функция:

wait_for_next = 0
number_was_cut = 0
last_number = 0

do
{
    1. down(mutex2)

    2. if (number_was_cut && !isdigit(buffer[buffer_index]))
           break     /* turns out it wasn't really cut
                        as beginning of next buffer is ' ' */
       number_was_cut = 0
       wait_for_next = 0

    3. while (buffer_index < data_length && !isdigit(buffer_index[buffer_index]))
           ++buffer_index;    /* skip white space */

    4. while (buffer_index < data_length && isdigit(buffer[buffer_index]))
           last_number = last_number * 10 + buffer[buffer_index++] - '0';

    5. if (buffer_index >= data_length && !there_was_space_after)
           number_was_cut = 1
           wait_for_next = 1
           up(mutex1)         /* let more data come in */
       else
           up(mutex2)         /* let get_int continue */
           break
} while (wait_for_next)

return last_number

data_processor (например):

int first_num = get_int()
int sencod_num = get_int()
for i = first_num to second_num
    do_whatever(get_int())

Объяснение: Во-первых, см. data_processor. Он не участвует в осложнениях в отношении того, как считываются данные, поэтому он просто получает целые числа и делает с ними все, что захочет. Теперь давайте посмотрим /proc читателя. Он в основном ждет, пока data_processor вызовет get_int достаточно времени для всех текущих данных, которые будут потребляться (шаг 1), а затем копирует следующий буфер во внутреннюю память, позволяя продолжить data_processor (шаг 2). Затем ему нужно разделить конечные пробелы, чтобы get_int можно было упростить (шаг 3). Наконец, он сигнализирует get_int, что он может начать считывать данные (шаг 4).

Функция get_int сначала ожидает получения данных (шаг 1), (теперь игнорируйте шаг 2), она пропускает любые нежелательные символы (шаг 3), а затем начинает считывать номер (шаг 4). Конец чтения номера - две возможности; достигается конец буфера (в этом случае, если/proc-считыватель не разделил никаких пробелов, тогда число могло бы быть сокращено между кадрами) или выполняется пробел. В первом случае для чтения/чтения данных требуется считывать данные /proc и ждать, пока другой цикл добавит остальную часть номера в текущую, и в последнем случае он вернет номер (шаг 5). Если продолжить с последнего кадра, проверьте, начинается ли новый кадр с номера или нет. Если нет, то предыдущее число было фактически целым числом и должно быть возвращено. В противном случае ему необходимо продолжить добавление цифр к последнему числу (шаг 2).

Проблема

Основная проблема с этим методом заключается в том, что он слишком сложный. Это становится намного сложнее при добавлении get_string, или целое число чтения может быть шестым и т.д. В принципе, вам нужно повторно изобрести sscanf! Обратите внимание, что sscanf можно использовать в этом простом примере на шаге 4 из get_int вместо цикла while (или также с get_string), но это становится более сложным, если также возможен вход в шестнадцатеричный (представьте себе шестнадцатеричный число, вырезанное между 0 и x0212ae4). Тем не менее, он просто заменяет шаг 4 get_int, и остальная часть материала все равно останется.

На самом деле у меня было много ошибок и тяжелых тестов, чтобы улучшить все особые случаи. Это еще одна причина, почему это не выглядит элегантно для меня.

Вопросы

Я хотел бы знать, есть ли какой-нибудь лучший способ справиться с этим. Я знаю, что использование разделяемой памяти может быть вариантом, но я ищу алгоритм для этой задачи (больше из любопытства, так как у меня уже есть мое рабочее решение). Более конкретно:

  • Есть ли уже реализованный метод в ядре Linux, который можно рассматривать как обычный C FILE, из которого вы можете брать данные, и обрабатывает разбиение данных на самих страницах?
  • Если нет, я слишком усложняю вещи, и я не вижу очевидного простого решения?
  • Я считаю, что fscanf сталкивается с аналогичной проблемой. Как с этим справиться?

Боковой вопрос: неужели это ужасно, что я блокирую /proc читателя на мьютексе? Я имею в виду, что запись данных может быть заблокирована, но я не уверен, что это обычно происходит в пространстве пользователя или в ядре.

Ответ 1

Наконец-то я решил написать что-то подходящее для решения этой проблемы.

kio

kio короче, будет стандартным портом стандарта C stdio.h для модулей ядра. Он будет поддерживать файловые системы /proc, /sys и /dev в режимах чтения и записи, будь то текстовые или двоичные. kio внимательно следит за стандартом, но имеет свои незначительные настройки для обеспечения безопасности в пространстве ядра.

Текущее состояние:

  • /proc файлы могут быть созданы
  • Выполняются функции чтения
  • Функции записи реализованы
  • Файлы могут открываться только пользователями один раз за раз

Ответ 2

Интерфейс request_firmware() может вас заинтересовать; все это буферизуется ядром до его передачи вам.

В противном случае, возможно, интерфейс двоичных атрибутов sysfs более полезен, чем proc?

Ответ 3

Внедрите отдельную функцию get_token(), которая:

  • Считывает из внутреннего буфера, пока не найдет пробел, а затем вернет текст.
  • Когда в конце буфера читается больше данных из пользовательского пространства в буфер.

Таким образом, ваша другая логика синтаксического анализа значительно упрощается (не нужно анализировать целые числа вручную и т.д.). Просто вызовите sscanf() или strtol() в возвращаемых строках.

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