Linux Device Drivers 3 / Драйверы устройств Линукс (третье издание): 4.3. Отладка через запросы
| Английский оригинал | Перевод на русский | |
|---|---|---|
Сожалеем, но текст оригинала доступен только зарегистрированным пользователям. |
Отладка через запросы hardorsoft 19.11.10 в 12:24 |
|
|
В предыдущем разделе мы описали, как работает printk, и как можно её использовать. Но пока ничего не говорили о её недостатках. hardorsoft 19.11.10 в 12:24 |
||
|
Массовое использование printk может заметно замедлить работу системы, даже если вы понизили console_loglevel во избежание загрузки консольного устройства, потому что syslogd поддерживает синхронизацию своих выходных файлов; таким образом, каждая печатаемая строка является причиной дисковой операции. Это правильно с точки зрения syslogd. Он пытается записать всё на диск на случай падения системы сразу после печати сообщения; однако, вы не хотите замедлить работу системы только ради отладочных сообщений. Эта проблема может быть решена с помощью префикса с дефисом в имени вашего файла протокола, как это показано в /etc/syslogd.conf. Проблемой с изменением конфигурационного файла является то, что изменение, вероятно, останется там и после окончания отладки, хотя при нормальной работе системы вы хотите, чтобы сообщения сбрасывались на диск как можно скорее. Альтернативой такому постоянному изменению является работа другой программы вместо klogd (такой, как cat /proc/kmsg, как предлагалось ранее), но это не может обеспечить благоприятные условия для нормального функционирования системы. hardorsoft 19.11.10 в 12:25 |
||
|
(* Дефис, или знак минус, это "магический" маркер syslogd для предотвращения сброса на диск каждого нового сообщения, документированный в syslog.conf (5), описание стоит прочитать.) hardorsoft 19.11.10 в 12:25 |
||
|
Чаще всего лучшим способом для получения соответствующей информации является запрос к системе когда вам нужна информация, а не постоянный вывод данных. По сути, каждая система Unix предлагает множество инструментов для получения системной информации: ps, netstat, vmstat и так далее. hardorsoft 19.11.10 в 12:26 |
||
|
Для разработчиков драйверов для запросов к системе доступны несколько методов: создание файла в файловой системе /proc, используя метод драйвера ioctl, и экспорт атрибутов через sysfs. Использование sysfs требует некоторой информации о драйверной модели. Это обсуждается в Главе 14. hardorsoft 19.11.10 в 12:27 |
||
|
Использование файловой системы /proc hardorsoft 19.11.10 в 12:27 |
||
|
Файловая система /proc является специальной программно созданной файловой системой, которая используется ядром для экспорта информации в мир. Каждый файл в /proc связан с функцией ядра, которая порождает "содержимое" файла на лету, когда файл читается. Мы уже видели некоторые из этих файлов в действии; /proc/modules, например, всегда возвращает список загруженных модулей. hardorsoft 19.11.10 в 12:27 |
||
|
/proc широко используется в системе Linux. Многие утилиты в современных дистрибутивах Linux, такие как ps, top и uptime получают свою информацию из /proc. Некоторые драйверы также экспортируют информацию через /proc и ваш может делать то же самое. Файловая система /proc динамическая, так что ваш модуль может добавлять или удалять записи в любое время. hardorsoft 19.11.10 в 12:27 |
||
|
Полнофункциональные записи в /proc могут быть сложными созданиями; среди прочего, они могут быть записываемыми, также как и читаемыми. Однако, в большинстве случаев записи в /proc являются только читаемыми файлами. Этот раздел имеет дело с простым случаем "только для чтения". Те, кто заинтересован в осуществлении чего-то более сложного, могут посмотреть здесь основу; затем для полной картины можно посмотреть исходный код ядра. hardorsoft 19.11.10 в 12:27 |
||
|
Однако, прежде чем мы продолжим, следует отметить, что добавление файлов в каталоге /proc не приветствуется. Файловая система /proc рассматривается разработчиками ядра как немного неконтролируемый беспорядок, который вышел далеко за пределы своей первоначальной цели (которая была в предоставлении информации о процессах, запущенных в системе). Рекомендуемый способ предоставления информации в новом коде - через sysfs. Однако, работа с sysfs требует понимания драйверной модели в Linux и мы не сможем сделать это, пока не дойдём до Главы 14. Между тем, файлы в /proc создать намного легче и они полностью подходят для отладки, поэтому мы и рассмотрим их здесь. hardorsoft 19.11.10 в 12:29 |
||
|
Работа с файлами в /proc hardorsoft 19.11.10 в 12:30 |
||
|
Все модули, которые работают с /proc, должны для определения соответствующих функций подключать <linux></linux>proc_fs.h>. hardorsoft 19.11.10 в 12:30 |
||
|
Чтобы создать файл только для чтения в /proc, ваш драйвер должен содержать функцию для предоставления данных, когда файл читается. Когда какой-то процесс читает файл (используя системный вызов call), запрос достигает модуля с помощью этой функции. Мы рассмотрим эту функцию первой и далее в этом разделе доберёмся до регистрации интерфейса. hardorsoft 19.11.10 в 12:32 |
||
|
Когда процесс читает из вашего файла в /proc, ядро выделяет страницу памяти (то есть PAGE_SIZE байт), куда драйвер может записать данные для возврата в пространство пользователя. Этот буфер передаётся в вашу функцию, которая представляет собой метод, названный read_proc: hardorsoft 19.11.10 в 12:32 |
||
|
int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data); hardorsoft 19.11.10 в 12:32 |
||
|
Указатель page является буфером, куда вы будете записывать ваши данные; start используется функцией, чтобы сказать, где на странице были записаны интересующие данные (подробнее об этом позже); offset и count имеют такое же значение, как и для метода read. Аргумент eof указывает на целое число, которое должно быть установлено драйвером, чтобы сигнализировать, что у него нет больше данных для возвращения, в то время как data является указателем на специфические данные драйвера, которые можно использовать для внутренней бухгалтерии. hardorsoft 19.11.10 в 12:33 |
||
|
Эта функция должна возвращать количество байт данных, фактически размещённых в буфере page, так же как делает метод read для других файлов. Другие выходные значения - это *eof и *start. eof - это простой флаг, а вот использование значения start является несколько более сложным; его целью является помощь в реализации больших (более одной страницы) /proc файлов. hardorsoft 19.11.10 в 12:33 |
||
|
Параметр start имеет несколько нетрадиционное использование. Его целью является указать, где (в пределах страницы) находятся данные, которые будут возвращены пользователю. Когда вызывается метод proc_read, *start будет NULL. Если вы оставите его NULL, ядро предполагает, что данные были помещены на страницу, как если бы offset был 0; другими словами, оно предполагает простую версию proc_read, которая размещает всё содержимое виртуального файла на страницу, не обращая внимания на параметр смещения. Вместо этого, если вы установите *start в значение не-NULL, ядро предполагает, что данные, на которые указывает *start, принимают во внимание offset и готовы быть возвращены непосредственно к пользователю. В общем, простые методы proc_read, которые возвращают крошечные объёмы данных, просто игнорируют start. Более сложные методы устанавливают *start к page и размещают данные только начиная с запрошенного здесь смещения. hardorsoft 19.11.10 в 12:33 |
||
|
Давно существует другой важный вопрос с файлами /proc, которые также призван решить start. Иногда между последовательными вызовами read изменяется ASCII представление структур данных ядра, так что читающий процесс может обнаружить противоречивые данные от одного вызова к другому. Если *start установлен как небольшое целое значение, вызывающий использует его для увеличения filp->f_pos независимо от объёма возвращаемых данных, тем самым делая f_pos внутренним номером записи вашей процедуры read_proc. Если, например, ваша функция read_proc возвращает информацию из большого массива структур и пять из этих структур были возвращены в первом вызове, *start мог бы быть установлен в 5. Следующий вызов предоставляет то же значение, как offset; драйвер теперь знает, что надо начинать возвращать данные с шестой структуры в массиве. Это признается как "взлом" его авторами и это можно увидеть в fs/proc/generic.c. hardorsoft 19.11.10 в 12:33 |
||
|
Отметим, что существует лучший путь для реализации больших /proc файлов; он называется seq_file и мы обсудим его в ближайшее время. Сейчас же настало время для примера. Вот простая (хотя и несколько некрасивая) реализация read_proc для устройства scull: hardorsoft 19.11.10 в 12:33 |
||
|
int scull_read_procmem(char *buf, char **start, off_t offset, int count, int *eof, void *data) hardorsoft 19.11.10 в 12:34 |
||
|
Это довольно типичная реализация read_proc. Она предполагает, что никогда не будет необходимо создать более одной страницы данных и, таким образом, игнорирует значения start и offset. Однако, на всякий случай, делает это осторожно, чтобы не переполнить буфер. hardorsoft 19.11.10 в 12:34 |
||
|
Устаревший интерфейс hardorsoft 04.12.10 в 02:04 |
||
|
Если вы почитаете исходный код ядра, то можете столкнуться с кодом реализующим /proc файлы со старым интерфейсом: hardorsoft 04.12.10 в 02:04 |
||
|
int (*get_info)(char *page, char **start, off_t offset, int count); hardorsoft 04.12.10 в 02:05 |
||
|
Все эти аргументы имеют тот же смысл, что и для read_proc, но аргументы eof и data отсутствуют. Этот интерфейс всё ещё поддерживается, но он может уйти в будущем; новый код должен использовать взамен ему интерфейс read_proc. hardorsoft 04.12.10 в 02:05 |
||
|
Создание вашего файла в /proc hardorsoft 04.12.10 в 02:05 |
||
|
После того, как функция read_proc определена, необходимо подключить её к записи в иерархии /proc. Это делается с помощью вызова create_proc_read_entry: hardorsoft 04.12.10 в 02:05 |
||
|
struct proc_dir_entry *create_proc_read_entry(const char *name, hardorsoft 04.12.10 в 02:05 |
||
|
Здесь, name это имя файла для создания, mode является маской защиты файла (может быть передана как 0 для общесистемного значения по умолчанию), base указывает каталог, в котором должен быть создан файл (если base равен NULL, файл создаётся в каталоге корне /proc), read_proc является функцией read_proc, которая реализует файлов и data игнорируется ядром (но передаётся в read_proc). Вызов, использующийся scull, чтобы создать функцию /proc, доступную как /proc/scullmem: hardorsoft 04.12.10 в 02:06 |
||
|
create_proc_read_entry("scullmem", 0 /* режим по умолчанию */, hardorsoft 04.12.10 в 02:06 |
||
|
Здесь мы создаём файл с именем scullmem прямо в /proc, с защитой со значением по умолчанию, для чтения всеми. hardorsoft 04.12.10 в 02:06 |
||
|
Указатель директории может быть использован для создания всей иерархии каталогов в /proc. Однако, следует отметить, что запись может быть более легко помещена в подкаталог /proc просто указанием имени каталога в качестве части названия записи, если сам каталог уже существует. Например, (часто игнорирующееся) соглашение говорит, что записи в /proc, связанные с драйверами устройств, должны вести в подкаталог driver/; scull мог бы поместить свою запись там просто задав своё имя как driver/scullmem. hardorsoft 04.12.10 в 02:07 |
||
|
Записи в /proc, конечно же, должны быть удалены при выгрузке модуля. remove_proc_entry является функцией, которая отменяет уже сделанную create_proc_read_entry: hardorsoft 04.12.10 в 02:07 |
||
|
remove_proc_entry("scullmem", NULL /* parent dir */); hardorsoft 04.12.10 в 02:07 |
||
|
Сбой при удалении записей может привести к вызовам в нежелательное время, или, если ваш модуль был выгружен, аварийному отказу ядра. hardorsoft 04.12.10 в 02:07 |
||
|
При использовании файлов /proc, как было показано, вы должны помнить несколько неудобств реализации - неудивительно, что его использование не рекомендуется в настоящее время. hardorsoft 04.12.10 в 02:08 |
||
|
Наиболее важная проблема связана с удалением записей в /proc. Такое удаление может случиться во время использования файла, так как нет владельца, связанного с записями /proc, поэтому их использование не воздействует на счётчик ссылок на модуль. Эта проблема, к примеру, может быть легко получена запуском sleep 100 < /proc/myfile перед удалением модуля. hardorsoft 04.12.10 в 02:08 |
||
|
Другой вопрос связан с регистрацией двух записей с одинаковыми именами. Ядро доверяет драйверу и не проверяет, зарегистрировано ли уже это имя, так что если вы не будете осторожны, то в можете доиграться до двух или более записей с тем же названием. Такие записи неразличимы и при доступе к ним, и когда вы вызываете remove_proc_entry. hardorsoft 04.12.10 в 02:08 |
||
|
Интерфейс seq_file hardorsoft 04.12.10 в 02:08 |
||
|
Как уже отмечалось выше, реализация больших файлов в /proc немного затруднительна. С течением времени методы /proc стали пользоваться дурной славой из-за ошибочных реализаций, когда объём вывода сильно увеличивается. Для очистки кода /proc и облегчения жизни программистам ядра был добавлен интерфейс seq_file. Этот интерфейс предоставляет простой набор функций для реализации больших виртуальных файлов ядра. hardorsoft 04.12.10 в 02:08 |
||
|
Интерфейс seq_file предполагает, что вы создаёте виртуальный файл, который шагает через последовательность элементов, которые должны быть возвращены в пространство пользователя. Для использования seq_file вы должны создать простой объект "итератор", который может содержать позицию в последовательности, шагнуть вперёд и вывести один элемент последовательности. Это может показаться сложным, но по сути этот процесс довольно прост. Чтобы показать, как это делается, мы пройдём через создание файла /proc в драйвере scull. hardorsoft 04.12.10 в 02:09 |
||
|
Первый неизбежным шагом является включение <linux></linux>seq_file.h>. Затем вы должны создать четыре итерационных метода, названных start, next, stop и show. hardorsoft 04.12.10 в 02:09 |
||
|
Метод start всегда вызывается первым. Прототип этой функции: hardorsoft 04.12.10 в 02:09 |
||
|
void *start(struct seq_file *sfile, loff_t *pos); hardorsoft 04.12.10 в 02:09 |
||
|
Аргументом sfile почти всегда можно пренебречь. pos является целой позицией, указывающей, где должно начаться чтение. Интерпретация позиции полностью зависит от реализации; она не должна быть позицией байта в результирующем файле. В реализациях seq_file обычно шагающих через последовательность интересующих объектов, позиция часто интерпретируется как курсор, указывающий на следующий элемент последовательности. Драйвер scull интерпретирует каждое устройство как один элемент последовательности, так что входящее значение pos - просто индекс в массиве scull_devices. Таким образом, метод start, используемый в scull: hardorsoft 04.12.10 в 02:09 |
||
|
static void *scull_seq_start(struct seq_file *s, loff_t *pos) hardorsoft 04.12.10 в 02:10 |
||
|
Возвращаемое значения, если не NULL, является внутренней величиной, которая может быть использована реализацией итератора. hardorsoft 04.12.10 в 02:10 |
||
|
Следующая функция должна сдвигать итератор на следующую позицию, возвращая NULL, если в последовательности ничего не осталось. Прототип этого метода: hardorsoft 04.12.10 в 02:10 |
