Linux Device Drivers 3 / Драйверы устройств Линукс (третье издание): 3.7. Методы read и write
| Английский оригинал | Перевод на русский | |
|---|---|---|
Сожалеем, но текст оригинала доступен только зарегистрированным пользователям. |
3.7 read и write armagetron 31.10.10 в 09:26 |
|
|
Методы read и write выполняют аналогичные задачи, то есть копирование данных из и в код приложения. Таким образом, их прототипы очень похожи и стоит представить их в одно время: hardorsoft 19.11.10 в 10:23 |
||
|
ssize_t read(struct file *filp, char _ _user *buff, size_t count, loff_t *offp); hardorsoft 29.10.10 в 23:07 |
||
|
Для обоих методов filp является указателем на file и count - это размер запрашиваемой передачи данных. Аргумент buff указывает на пользовательский буфер данных, удерживающий данные, которые будут записаны, или пустой буфер, в котором должны быть размещены вновь считанные данные. Наконец, offp является указателем на объект "типом длинного смещения" (“long offset type”), который указывает на позицию файла, к которой обращается пользователь. Возвращаемое значение является "типом знакового размера" (“signed size type”); его использование будет рассмотрено позже. hardorsoft 19.11.10 в 10:23 |
||
|
Давайте повторим, что аргумент buff для методов read и write является указателем пространства пользователя. Поэтому он не может быть непосредственно разыменовываться кодом ядра. Есть несколько причин для этого ограничения: hardorsoft 19.11.10 в 10:24 |
||
|
В зависимости от архитектуры, на которой работает ваш драйвер, и как было сконфигурировано ядро, указатель пользовательского пространства может совсем не быть действительным во время работы в режиме ядра. Может не быть ничего связанного с этим адресом, или он может указывать на какие-то другие, случайные данные. hardorsoft 19.11.10 в 10:24 |
||
|
Даже если указатель означает что-то в пространстве ядра, память пользовательского пространства организована странично и память в запросе может не находиться в ОЗУ, когда сделан этот системный вызов. Попытка сослаться на память пользовательского пространства непосредственно может сгенерировать ошибку страничного доступа, это то, что код ядра не разрешает делать. Результатом будет "Ой" (“oops”), которое приведёт к гибели процесса, который сделал этот системный вызов. hardorsoft 19.11.10 в 10:24 |
||
|
Указатель в запросе поступает от пользовательской программы, которая могла бы быть ошибочной или злонамеренной. Если же ваш драйвер слепо разыменовывает переданный пользователем указатель, он представляет собой открытую дверь, позволяющую программе пользовательского пространства получить доступ или перезаписать память где угодно в системе. Если вы не хотите нести ответственность за ущерб безопасности системам ваших пользователей, вы никогда не можете разыменовывать указатель пользовательского пространства напрямую. hardorsoft 19.11.10 в 10:24 |
||
|
Очевидно, что ваш драйвер должен иметь возможность доступа в буфер пользовательского пространства для того, чтобы выполнить свою работу. Однако, чтобы быть безопасным, этот доступ должен всегда быть выполнен через специальные функции, поставляемые для того ядром. Приведём некоторые из этих функций (которые определены в <asm></asm>uaccess.h>) здесь, а остальные в разделе "Использование аргумента ioctl" в Главе 6; они используют некоторую особую, архитектурно-зависимую магию, чтобы гарантировать, что передача данных между ядром и пользовательским пространством происходит безопасным и правильным способом. hardorsoft 19.11.10 в 10:24 |
||
|
Коду для read и write в scull необходимо скопировать весь сегмент данных в или из адресного пространства пользователя. Эта возможность преолставляется следующими функциями ядра, которые копируют произвольный массив байтов и находятся в основе большинства реализаций read и write: hardorsoft 19.11.10 в 10:25 |
||
|
unsigned long copy_to_user(void _ _user *to, const void *from, unsigned long count); hardorsoft 29.10.10 в 23:08 |
||
|
Хотя эти функции ведут себя как нормальные функции memcpy, следует применять немного дополнительной осторожности, когда из кода ядра адресуется пространство пользователя. Адресуемые пользовательские страницы могут не быть в настоящее время в памяти и подсистема виртуальной памяти может отправить процесс в спячку, пока страница не будет передана на место. Это происходит, например, когда страница должна быть получена из свопа. Конечный результат для автора драйвера в том, что любая функция, которая осуществляет доступ к пространству пользователя должна быть повторно-входимой, должна быть в состоянии выполняться одновременно с другими функциями драйвера, и, в частности, должна быть в состоянии, когда она может законно спать. Мы вернемся к этому вопросу в Главе 5. hardorsoft 19.11.10 в 10:25 |
||
|
Роль этих двух функций не ограничивается копированием данных в и из пространства пользователя: они также проверяют, является ли указатель пользовательского пространства действительным. Если указатель является недействительным, копирование не выполняется, с другой стороны, если неверный адрес встречается во время копирования, копируется только часть данных. В обоих случаях возвращается значение объёма памяти, которое всё же скопировано. Код scull смотрит на возвращаемую ошибку и возвращает -EFAULT пользователю, если значение не 0. hardorsoft 19.11.10 в 10:25 |
||
|
Тема доступа в пространство пользователя и недействительных указателей пространства пользователя более широка и обсуждается в Главе 6. Однако, стоит заметить, что если вам не требуется проверять указатель пользовательского пространства, вы можете вызвать взамен __copy_to_user и __copy_from_user. Это полезно, например, если вы знаете, что уже проверили аргумент. Тем не менее, будьте осторожны, если в действительности вы не проверяете указатель пользовательского пространства, который вы передаёте в эти функции, вы можете создать аварии ядра и/или дыры в системе безопасности. hardorsoft 19.11.10 в 10:25 |
||
|
Что же касается самих методов устройства, задача метода read - скопировать данные из устройства в пространство пользователя (используя copy_to_user), а метод write должен скопировать данные из пространства пользователя на устройство (с помощью copy_from_user). Каждый системный вызов read или write запрашивает передачу определённого числа байт, но драйвер может свободно передать меньше данных - точные правила немного отличаются для чтения и записи и описаны далее в этой главе. hardorsoft 19.11.10 в 10:25 |
||
|
Независимо от количества переданных методами данных, обычно они должны обновить позицию файла в *offp, представляющего текущую позицию в файле после успешного завершения системного вызова. Затем когда требуется, ядро передаёт изменение позиции файла обратно в структуру file. Однако, системные вызовы pread и pwrite имеют различную семантику; они работают от заданного смещения файла и не изменяют позицию файла, видимую любым другим системным вызовом. Эти вызовы передают указатель на позицию, заданную пользователем, и отменяют изменения, которые делает ваш драйвер. hardorsoft 19.11.10 в 10:25 |
||
|
Рисунок 3-2 показывает, как использует свои аргументы типичная реализация read. hardorsoft 19.11.10 в 10:26 |
||
|
Рисунок 3-2. Аргументы для чтения hardorsoft 19.11.10 в 10:26 |
||
|
Оба метода, read и write, если произошла ошибка, возвращают отрицательное значение. Вместо этого, возвращаемое значение большее или равное 0, говорит вызывающей программе, сколько байт было успешно передано. Если какие-то данные передаются правильно и затем происходит ошибка, возвращаемое значение должно быть числом успешно переданных байт и об ошибке не сообщается до следующего вызова функции. Конечно, выполнение этого правила требует, чтобы ваш драйвер помнил о том, что произошла ошибка, чтобы он мог вернуть статус ошибки в будущем. hardorsoft 19.11.10 в 10:27 |
||
|
Хотя функции ядра возвращают отрицательное число как сигнал об ошибке, а значение числа показывает вид произошедшей ошибки (как рассказывалось в Главе 2), программы, которые выполняются в пользовательском пространстве, всегда видят -1 в качестве возвращаемого ошибочного значения. Они должны получить доступ к переменной errno, чтобы выяснить, что случилось. Поведение в пользовательском пространстве диктуется стандартом POSIX, но этот стандарт не предъявляет требований к внутренним операциям ядра. hardorsoft 19.11.10 в 10:27 |
||
|
3.7.1 Метод read. armagetron 31.10.10 в 09:30 |
||
|
Возвращаемое значение для read интерпретируется вызывающей прикладной программой: hardorsoft 19.11.10 в 10:34 |
||
|
Если значение равно аргументу count, переданному системному вызову read, передано запрашиваемое количество байт. Это оптимальный случай.ый вызов до завершения запрошенной передачи данных. hardorsoft 19.11.10 в 10:34 |
||
|
Если число положительное, но меньше, чем count, только часть данных была передана. Это может произойти по ряду причин, в зависимости от устройства. Чаще всего прикладная программы повторяет чтение. Например, если вы читаете с помощью функции fread, библиотечная функция повторяет системный вызов до завершения запрошенной передачи данных. hardorsoft 19.11.10 в 10:38 |
||
|
Если значение равно 0, был достигнут конец файла (и данные не были прочитаны). hardorsoft 19.11.10 в 10:38 |
||
|
Отрицательное значение означает, что произошла ошибка. Значение указывает, какая была ошибка согласно <linux></linux>errno.h>. Типичные возвращаемые значения ошибки включают -EINTR (прерванный системный вызов) или -EFAULT (плохой адрес). hardorsoft 19.11.10 в 10:39 |
||
|
Что отсутствует в этом списке, так это случай "нет данных, но они могут прийти позже". В этом случае системный вызов read должен заблокироваться. Мы будем работать с блокирующим вводом в Главе 6. hardorsoft 19.11.10 в 10:39 |
||
|
Код scull использует эти правила. В частности, он пользуется правилом частичного чтения. Каждый вызов scull_read имеет дело только с одним квантом данных не создавая цикл, чтобы собрать все данные; это делает код короче и проще для чтения. Если читающая программа действительно хочет больше данных, она повторяет вызов. Если для чтения устройства используется стандартная библиотека ввода/вывода (например, fread), приложение даже не заметит квантования при передаче данных. hardorsoft 19.11.10 в 10:39 |
||
|
Если текущая позиция чтения больше размера устройства, метод read драйвера scull возвращает 0, чтобы сигнализировать, что данных больше нет (другими словами, мы в конце файла). Эта ситуация может произойти, если процесс А читает устройство в то время, как процесс Б открывает его для записи, тем самым укорачивая размер устройства до 0. Процесс вдруг обнаруживает себя в конце файла и следующий вызов read возвращает 0. hardorsoft 19.11.10 в 10:39 |
||
|
Вот код для read (игнорируйте сейчас вызовы down_interruptible и up, мы узнаем о них в следующей главе): hardorsoft 19.11.10 в 10:40 |
||
|
ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) hardorsoft 19.11.10 в 10:40 |
||
|
3.7.1 Метод write. armagetron 31.10.10 в 09:31 |
||
|
write, как и read, может передавать меньше данных, чем было запрошено в соответствии со следующими правилами для возвращаемого значения: hardorsoft 19.11.10 в 10:40 |
||
|
Если значение равно count, передано запрашиваемое количество байт. hardorsoft 19.11.10 в 10:40 |
||
|
Если число положительное, но меньше, чем count, была передана только часть данных. Программа, скорее всего, повторит запись остальных данных. hardorsoft 19.11.10 в 10:40 |
||
|
Если значение равно 0, ничего не записано. Этот результат не является ошибкой и нет никаких оснований для возвращения кода ошибки. И снова стандартная библиотека повторит вызов write. Мы рассмотрим точное значение этого случая в Главе 6, где познакомимся с блокирующей записью. hardorsoft 19.11.10 в 10:41 |
||
|
Отрицательное значение означает ошибку; как и для read, допустимые значения ошибок определены в <linux></linux>errno.h>. hardorsoft 19.11.10 в 11:29 |
||
|
К сожалению, всё равно могут быть неправильные программы, которые выдают сообщение об ошибке и прерываются, когда производится частичная передача. Это происходит потому, что некоторые программисты привыкли к вызовам write, которые либо ошибочны, либо полностью успешны, это происходит в большом количестве случаев и должно так же поддерживаться устройством. Это ограничение в реализации scull может быть исправлено, но мы не хотим усложнять код более, чем необходимо. hardorsoft 19.11.10 в 11:29 |
||
|
Код scull для write выполняется с одним квантом за раз, так же, как это делает метод read: hardorsoft 19.11.10 в 11:29 |
||
|
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) hardorsoft 19.11.10 в 11:30 |
||
|
3.7.3 readv и writev armagetron 31.10.10 в 09:31 |
||
|
Системы Unix уже давно поддерживают два системных вызова, названных readv и writev. Эти "векторные" версии read и write берут массив структур, каждая из которых содержит указатель на буфер и значение длины. Вызов readv будет читать указанное количество в каждый буфер по порядку. writev, вместо этого, будет собирать вместе содержимое каждого буфера и передавать их наружу как одну операцию записи. hardorsoft 19.11.10 в 11:30 |
||
|
Если драйвер не предоставляет методы для обработки векторных операций, readv и writev осуществляются несколькими вызовами ваших методов read и write. Однако, во многих случаях путём прямой реализации readv и writev достигается повышение эффективности. hardorsoft 19.11.10 в 11:30 |
||
|
Прототипами векторных операций являются: hardorsoft 19.11.10 в 11:31 |
||
|
Здесь аргументы filp и ppos такие же, как для read и write. Структура iovec, определённая в <linux></linux>uio.h>, выглядит следующим образом: hardorsoft 19.11.10 в 11:32 |
||
|
struct iovec hardorsoft 29.10.10 в 23:08 |
||
|
Каждая iovec описывает одну порцию данных для передачи; она начинается в iov_base (в пространстве пользователя) и имеет длину iov_len байт. Параметр count говорит методу, сколько существует структур iovec. Эти структуры созданы приложением, но ядро копирует их в пространство ядра до вызова драйвера. hardorsoft 19.11.10 в 11:31 |
||
|
Простейшей реализацией векторных операций будет прямой цикл, который просто передаёт адрес и длину каждой iovec функциям драйвера read или write. Часто, однако, эффективное и правильное поведение требует, чтобы драйвер делал что-то поумнее. Например, writev на ленточном накопителе должна сохранить содержимое всех структур iovec одной операцией записи на магнитную ленту. hardorsoft 19.11.10 в 11:32 |
||
|
Однако, многие драйверы не получили никакой выгоды от реализации самими этих методов. Таким образом, scull опускает их. Ядро эмулирует их с помощью read и write, и конечный результат тот же. hardorsoft 19.11.10 в 11:33 |
