Библиотека сайта rus-linux.net
Драйверы устройств в Linux
Часть 6: Декодирование файловых операций для символьного устройства
Оригинал: "Device Drivers, Part 6: Decoding Character Device File Operations"Автор: Anil Kumar Pugalia
Дата публикации: May 1, 2011
Перевод: Н.Ромоданов
Дата перевода: июнь 2012 г.
В этой статье, которая является частью серии статей о драйверах устройств в Linux, по-прежнему, как и в предыдущих двух статьях, рассматриваются различные понятия, относящиеся к символьным драйверам и их реализации.
Итак, как, по-вашему, Светлана могла бы решить эту проблему? Ясно, что с помощью Пагса. Разве это не очевидно? В нашей предыдущей статье мы видели, что Светлана была озадачена тем, что она не могла прочитать какие-нибудь данные даже после их записи в файл символьного устройства /dev/mynull
. Вдруг раздался звонок — не у нее в голове, а реальный - в дверь. И конечно, это был Пагс.
"Как ты здесь оказался?" - воскликнула Светлана.
"Я увидел в твиттере твое сообщение. Это здорово, что ты самостоятельно написала полностью свой первый символьный драйвер. Это удивительно. Итак, на какой стадии ты сейчас находишься?" - спросил Пагс.
"Я скажу тебе только при условии, что ты не будешь вмешиваться" — ответила Светлана.
Пагс улыбнулся: "Хорошо, я только дам тебе совет".
"И о советах тоже - только если я попрошу совета! Я попытаюсь разобраться с работой файловых операций с символьным устройством" — сказала Светлана.
Пагс оживился и сказал: "У меня есть идея. Почему бы тебе не разобраться с ними, а затем объяснить, что ты о них поняла?"
Светлана поняла, что эта идея хорошая. Она применила команду tail
к журналу dmesg
для того, чтобы проследить за данными, выдаваемыми командой printk
из драйвера. Для того, чтобы было удобнее отслеживать работу с файловыми операциями my_open
, my_close
, my_read
и my_write
, она также открыла на своей консоли код своего null драйвера.
static int my_open(struct inode *i, struct file *f) { printk(KERN_INFO "Driver: open()\n"); return 0; } static int my_close(struct inode *i, struct file *f) { printk(KERN_INFO "Driver: close()\n"); return 0; } static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off) { printk(KERN_INFO "Driver: read()\n"); return 0; } static ssize_t my_write(struct file *f, const char __user *buf, size_t len, loff_t *off) { printk(KERN_INFO "Driver: write()\n"); return len; }
Из того, что мы узнали ранее, возврат значения из функций my_open()
и my_close()
тривиален, типы возвращаемых значений - int, и обе функции возвращают нулевое значение, что означает успешное завершение.
Но, типы возвращаемых значений обоих функций my_read()
и my_write()
не int, а - ssize_t
. При дальнейшем исследовании заголовков ядра, оказалось, что возвращаемое значение должно быть словом со знаком. Итак, если возвращается отрицательное число , то обычно это ошибка. Но неотрицательное возвращаемое значение будет иметь дополнительный смысл. Для операции чтения, оно будет указывать количество читаемых байтов, а для операции записи, оно будет указывать количество записываемых байтов.
Чтение файла устройства
Чтобы разобраться в этом подробнее, следует заново рассмотреть
порядок выполнения операций. Давайте сначала возьмем операцию чтения.
Когда пользователь выполняет чтение из файла устройства /dev/mynull
,
этот системный вызов поступает в слой виртуальной файловой системы
(VFS), находящийся в ядре. VFS декодирует пару <major, minor>
и выясняет, что нужно перенаправить системный вызов в функцию драйвера my_read()
, которая зарегистрирована в виртуальной системе. Так что с этой точки зрения функция my_read()
вызывается у нас, писателей драйверов устройств, как запрос на чтение. И, следовательно, возвращаемое значение будет указывать лицу, сделавшему запрос (например, пользователю), сколько байтов они получают при запросе на чтение.
В нашем примере null-драйвера мы возвратили ноль - это означает, что доступных байтов данных нет или что, другими словами, был достигнут конец файла. И, следовательно, когда читается файл устройства, то независимо от того, что в него было записано, результат будет отсутствовать.
"Хм ... Так что, если я поменяю значение на 1, то получу ли я какие-нибудь данные?" - спросил для проверки Пагс.
Светлана помолчала, посмотрела на параметры функции my_read()
и
утвердительно ответила, но с оговоркой — передаваемые данные были бы
мусором, поскольку функция my_read()
на самом деле не заносит
данные в буфер buf
(переменная — буфер, которая является вторым параметром функции my_read()
и указывается пользователем). На самом деле, функция my_read()
должна записать данные в буфер buf
в соответствие со значением len
(третий параметр функции), количеством байтов, запрашиваемых пользователем.
Если более конкретно, число байтов, записываемых в буфер buf
, должно быть меньше или равно значению len
, а количество записанных байтов должно быть передано обратно в качестве возвращаемого значения. Нет, это не опечатка - в операции чтения писатели драйверов устройств "записывают" данные в буфер, который предоставляется пользователем. Мы (возможно) читаем данные из соответствующего устройства, а затем записываем эти данные в пользовательский буфер, так что пользователь может его прочитать.
"Это ты очень умно сделала" - с сарказмом сказал Пагс.
Запись в файл устройства
Операция записи действует наоборот. Пользователь предоставляет значение длины len
(третий параметр функции my_write()
), указывающий количество байтов данных, которые должны быть записаны и которые расположены в буфере buf
(второй параметр функции my_write()
). Функция my_write()
будет читать эти данные и, возможно, записывать их на соответствующее устройство, и возвратит число, равное количеству байтов, которые были успешно записаны.
"Ага! Вот почему все мои операции записи в /dev/mynull
выполнялись успешно, но, в действительности ничего не читалось и не записывалось" — воскликнула Светлана, обрадовавшись тому, что она полностью разобралась со всей последовательностью действий с файлами устройств.
Сохранение последнего символа
Поскольку Светлана не дала Пагсу никакой возможности ее поправить, Пагс решил ее подзадорить.
"Хорошо. Кажется, ты полностью разобралась с основами чтения/записи, так вот у меня к тебе есть
задачка. Можешь ли ты изменить функции my_read()
и my_write()
таким образом, чтобы всякий раз, когда я буду читать /dev/mynull
, я получал бы последний символ, записанный в /dev/mynull
?".
Светлана уверенно взялась за задачку и изменила my_read()
и my_write()
следующим образом, добавив статическую глобальную символьную переменную:
static char c; static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off) { printk(KERN_INFO "Driver: read()\n"); buf[0] = c; return 1; } static ssize_t my_write(struct file *f, const char __user *buf, size_t len, loff_t *off) { printk(KERN_INFO "Driver: write()\n"); c = buf[len – 1]; return len; }
"Почти, но что, если пользователь указал неверный буфер, или что, если пользовательский буфер окажется выгруженным. Не вызовет ли прямой доступ из пользовательского пространства к буферу buf
лишь аварийную ситуацию и проблемы в ядре?" - напал Пагс.
Светлана, не испугавшись, заглянула в раскрытые ею учебники и выяснила, что просто есть два API, которые в пользовательском пространстве обеспечивают безопасный доступ к буферу, а затем воспользовалась ими. Полностью разобравшись с API, она переписала приведенный выше фрагмент кода следующим образом:
static char c; static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off) { printk(KERN_INFO "Driver: read()\n"); if (copy_to_user(buf, &c, 1) != 0) return -EFAULT; else return 1; } static ssize_t my_write(struct file *f, const char __user *buf, size_t len, loff_t *off) { printk(KERN_INFO "Driver: write()\n"); if (copy_from_user(&c, buf + len – 1, 1) != 0) return -EFAULT; else return len; }
Затем Светлана повторила следующие обычные шаги сборки и проверки:
- Собрала драйвер (файл
.ko
) с помощью запуска командыmake
. - Загрузила драйвер с помощью команды
insmod
. - Сделала запись в
/dev/mynull
с помощью командыecho -n "Pugs" > /dev/ mynull
. - Выполнила чтение из
/dev/mynull
с помощью команды/dev/mynul
(остановка с помощью нажатия клавиш Ctrl+C). - Выгрузила драйвер с помощью команды
rmmod
.
При выполнении команды cat
над /dev/mynull
последовательность
символов s будет выдаваться бесконечно, поскольку функция my_read()
постоянно считывает последний символ. Поэтому для того, чтобы остановить бесконечное чтение,
вмешался Пагс и нажал клавиши Ctrl+C, а затем он попытался объяснить: "Если здесь поменять
на 'последний символ читать только один раз', то функция my_read()
должна в первый раз возвращать 1, и ноль — со второго раза.
Это можно сделать с помощью параметра off
(четвертый параметр функции my_read()
)".
Светлана услужливо кивнула головой, лишь только для укрепления эго Пагса.
К предыдущей статье | Оглавление | К следующей статье |