Простой пример функции io_read()
Простой пример функции io_read()
Чтобы проиллюстрировать, как ваш администратор ресурса мог бы возвращать клиенту данные, рассмотрим простейший администратор ресурса, который всегда возвращает одну и ту же строковую константу «Здравствуй, мир! ». Даже в таком простом случае необходимо учесть ряд моментов, как-то:
• согласование размера клиентской области данных с количеством данных, подлежащих возврату;
• обработка EOF;
• поддерживание контекстной информации (индекс lseek());
• обновление POSIX-информации stat().
Учет размеров областей данных
В нашем случае администратор ресурсов возвращает фиксированную строку длиной в 17 байт, то есть размер доступных данных точно известен и постоянен. Эти аналогично случаю с дисковым файлом, доступным только для чтения и содержащим рассматриваемую строку. Единственное реальное отличие состоит в том, что этот «файл» обеспечивается в нашей программе строкой:
char *data_string = "Здравствуй, мир! ";
С другой стороны, клиент может запросить чтение любого объема данных — один байт, 17 байт или более. Это должно отразиться на характеристиках вашей реализации io_read() ее умением согласовывать размер запрашиваемых клиентом данных с размером данных, имеющихся в наличии.
Обработка EOF
Особым случаем согласования размеров областей данных является обработка EOF для строки фиксированной длины. Как только клиент считал заключительный символ « », дальнейшие его попытки считать данные должны возвращать EOF.
Поддерживание контекстной информации
И «учет размеров областей данных», и «обработка EOF» требуют, чтобы в OCB, передаваемом вашей функции io_read(), поддерживалась контекстная информация — в частности, поле offset.
Обновление информации POSIX
И еще одно заключительное соображение: при чтении данных из ресурса должна обновляться POSIX-переменная времени доступа atime («access time» — «время доступа»). Это делается для того, чтобы клиентская функция stat() могла обнаружить, что к устройству кто-то обращался.
Собственно код
Ниже приведена программа, в которой учтены все вышеперечисленные моменты. Ниже мы ее последовательно проанализируем.
/*
* io.read1.c
*/
#include <stdio.h>
#include <errno.h>
#include <sys/neutrino.h>
#include <sys/iofunc.h>
// наша строка с данными
char* data_string = "Здравствуй, мир! ";
int io_read(resmgr_context_t* ctp, io_read_t* msg,
iofunc_ocb_t* ocb) {
int sts;
int nbytes;
int nleft;
int off;
int xtype;
struct _xtype_offset* xoffset;
// 1) Проверить, открыто ли устройство на чтение
if ((sts ==
iofunc_read_verify(ctp, msg, ocb, NULL)) != EOK) {
return sts;
}
// 2) проверить и обработать переопределение XTYPE
xtype = msg->i.xtype & _IO_XTYPE_MASK;
if (xtype == _IO_XTYPE_OFFSET) {
xoffset = (struct _xtype_offset*)(msg->i + 1);
off = xoffset->offset;
} else if (xtype = _IO_XTYPE_NONE) {
off = ocb->offset;
} else { // Неизвестный тип; игнорировать
return ENOSYS;
}
// 3) Сколько байт осталось?
nleft = ocb->attr->nbytes – off;
// 4) Сколько байт мы можем отдать клиенту?
nbytes = min(nleft, msg->i.nbytes);
// 5) Если возвращаем данные, отдать их клиенту
if (nbytes) {
MsgReply(ctp->rcvid, nbytes, data_string+off, nbytes);
// 6) Установить значение "atime" для POSIX stat()
ocb->attr->flags |=
IOFUNC_ATTR_ATIME | IOFUNC_ATTR_DIRTY_TIME;
// 7) Если индекс lseek() не равен _IO_XTYPE_OFFSET,
// увеличить его на число считанных байт
if (xtype == _IO_XTYPE_NONE) {
ocb->offset += nbytes;
}
} else {
// 8) Не возвращаем данные, просто разблокировать клиента
MsgReply(ctp->rcvid, EOK, null, 0);
}
// 9) Сказать библиотеке, что мы уже ответили сами
return _RESMGR_NOREPLY;
}
Этап 1
Здесь мы убедились, что клиентский вызов open() действительно запросил открытие устройства на чтение. Если бы клиент открыл устройство только на запись, а затем попытался выполнить чтение, это следовало бы расценивать как ошибку. В этом случае вспомогательная функция iofunc_read_verify() возвратила бы нам (затем мы — библиотеке, а библиотека — клиенту) EBADF, а не EOK.
Этап 2
Здесь мы проверили, указал ли клиент индивидуальное для данного сообщения переопределение типа (xtype-override) (например, потому что если мы открыли устройство в неблокирующем режиме, то это указало бы, что для данного конкретного запроса мы хотим задать блокирующее поведение). Отметим, что блокирующий аспект переопределенияа типа может быть отражён в последнем параметре функции iofunc_read_verify(), однако, поскольку мы приводим здесь упрощенный пример, мы передаем NULL, указывая этим, что этот вопрос нас не волнует.
Более важно, однако, посмотреть, как обрабатываются конкретные модификаторы xtype. Очень интересен, например, модификатор _IO_XTYPE_OFFSET, который, если присутствует, указывает на то, что принятое от клиента сообщение содержит смещение, и что операция чтения не должна изменять «текущую позицию файла» для данного файлового дескриптора (так делает, например, функция pread()). Если модификатор _IO_XTYPE_OFFSET не указан, то операция чтения может смело модифицировать «текущую позицию файла». Мы используем переменную хtype для сохранения xtype, содержавшегося в принятом сообщении, и переменную off для представления текущего смещения, которое мы должны будем использовать при обработке. Далее, на этапе 7, вы увидите еще кое-какие действия по обработке модификатора _IO_XTYPE_OFFSET.
Если присутствует иное переопределение xtype, чем _IO_XTYPE_OFFSET (и это не пустая команда _IO_XTYPE_NONE), мы отказываемся обрабатывать запрос и возвращаем ENOSYS. Это просто означает, что мы не знаем, как обрабатывать такую ситуацию, и поэтому возвращаем клиенту признак ошибки.
Этапы 3 и 4
Чтобы вычислить, сколько байт мы можем реально возвратить клиенту, мы выполняем этапы 3 и 4, в которых выясняется, сколько байт доступно у устройства (разность между полным объемом устройства, полученным из ocb->attr->nbytes, и текущим смещением в устройстве). Узнав, сколько байт осталось, мы выбираем наименьшее значение между размером этого остатка и количеством байт, которые клиент хочет прочитать. Например, у нас может остаться семь байт, а клиент захочет прочитать только два. В этом случае мы возвратим клиенту только два байта. И наоборот, если клиент захочет прочитать 4096 байт, а у нас осталось только семь, мы сможем возвратить ему только семь байт.
Этап 5
Теперь, вычислив, сколько байт мы намерены возвратить клиенту, нам нужно сделать ряд вещей в зависимости от того, возвращаем мы данные или нет. Если да, то мы просто отвечаем клиенту с данными сразу после проверки на этапе 5. Обратите внимание, что для возврата данных с корректного смещения мы используем data_string + off (смещение off вычисляется в зависимости от наличия переопределения типа). Отметьте также второй параметр функции MsgReply() — в документации он упоминается как «status» («код завершения»), но в этом случае мы используем его для возврата числа байт. Мы делаем так, потому что реализация клиентской функции read() знает, что значение, возвращаемое ее функцией MsgSendv() (а это, кстати, как раз и есть параметр status функции MsgReply()) представляет собой число реально прочитанных байт — это общеизвестное соглашение.
Этап 6
Поскольку мы возвращаем данные от устройства, мы знаем, что к устройству производился доступ. Мы устанавливаем биты IOFUNC_ATTR_ATIME и IOFUNC_ATTR_DIRTY_TIME в поле flags атрибутной записи. Это служит напоминанием для функции io_stat() о том, что время доступа стало недействительным, и перед выполнением ответа его следует скорректировать по системным часам. Если бы нам очень хотелось, мы могли бы записать текущее время в поле atime атрибутной записи и сбросить флаг IOFUNC_ATTR_DIRTY_TIME; однако, это было бы не очень-то эффективно, поскольку мы предполагаем получить от клиента значительно большее запросов типа read(), чем запросов типа stat(). Впрочем, ваши условия могут диктовать иначе.
Так какое же время видит клиент, когда он вызывает-таки функцию stat()? Функция iofunc_stat_default(), предоставляемая библиотекой администратора ресурсов, посмотрит на поле flags атрибутной записи, чтобы проверить, являются времена доступа (поля atime, ctime и mtime) корректными или нет. Если нет (как это было бы после вызова io_read() с возвратом данных), iofunc_stat_default() устанавливает нужные из них в значение текущего времени.
Этап 7
Теперь мы увеличиваем смещение lseek() на число возвращенных клиенту байт, но делаем это только в том случае, если не обрабатываем модификатор _IO_XTYPE_OFFSET. Это гарантирует, что в случае отсутствия флага _IO_XTYPE_OFFSET, если клиент вызовет функцию lseek() для определения текущей позиции, или (более важный случай) если клиент вызовет read() для чтения еще нескольких байт, смещение в ресурсе будет корректным. Если _IO_XTYPE_OFFSET установлен, мы оставляем содержащееся в ocb смещение в покое.
Этап 8
Сопоставьте этот этап с этапом 6. Здесь мы только разблокируем клиента и не выполняем больше никаких действий. Также обратите внимание, что функции MsgReply() не передается никакой области данных, потому что в этом случае данные мы не возвращаем.
Этап 9
И наконец, на этапе 9 мы выполняем действия, не зависящие от того, возвращаем мы данные клиенту или нет. Поскольку мы уже сами разблокировали клиента при помощи MsgReply(), мы, конечно же, не хотим, чтобы это попыталась сделать еще и библиотека администратора ресурсов. Поэтому мы сообщаем ей, что мы уже сделали это сами, возвратом _RESMGR_NOREPLY.
Эффективное применение других функций обмена сообщениями
Как вы помните из главы «Обмен сообщениями», мы упоминали еще несколько функций обмена сообщениями, а именно — функции MsgWrite(), MsgWritev() и MsgReplyv(). Повод, в связи с которым я снова упоминаю здесь эти функции, состоит в том, что ваша функция io_read() может быть превосходным местом для их применения. В простом примере, показанном выше, мы возвращали непрерывный массив данных из постоянного места в памяти. В реальной же жизни вам может понадобиться возвратить, скажем, множество фрагментов данных из различных выделенных вами буферов. Классическим примером такого случая является циклический буфер, который часто применяется, например, в драйверах последовательных устройств. Часть данных может быть размещена в конце буфера, другая часть — в начале. В этом случае для возврата обеих частей данных вам понадобилось бы передать MsgReplyv() двухэлементный вектор ввода/вывода (IOV), где первый элемент содержал бы адрес (и длину) «нижней» части данных, а второй — адрес (и длину) «верхней» части. Или же, если вы ожидаете прибытия данных частями, вы могли бы вместо этого использовать функции MsgWrite() или MsgWritev() для записи данных в адресное пространство клиента по мере их поступления, а затем выдать заключительный вызов MsgReply() или MsgReplyv(), чтобы разблокировать клиента. Как мы уже показали выше, функция MsgReply() может и не передавать никаких данных— вы можете использовать ее просто для того, чтобы разблокировать клиента.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Простой пример
Простой пример Начнем с простого примера: наложим переход на изменение фона ссылки. Когда пользователь будет наводить на ссылку, цвет ее фона будет меняться, и мы применим переход, чтобы сделать это изменение плавным. Такого эффекта раньше можно было добиться
Простой пример функции io_write()
Простой пример функции io_write() Это был простой пример функции io_read(); давайте теперь перейдем к функции io_write(). Основной камень преткновения, связанный с io_write(), — получить доступ к данным. Поскольку библиотека администратора ресурсов считывает лишь незначительную часть
Простой пример функции io_devctl()
Простой пример функции io_devctl() Клиентский вызов devctl() формально определен так:#include <sys/types.h>#include <unistd.h>#include <devctl.h>int devctl(int fd, int dcmd, void *dev_data_ptr, size_t nbytes, int *dev_info_ptr);Прежде чем рассматривать эту функцию с позиций администратора ресурсов, надо сначала понять, что это за
11.3.2. Простой пример stat()
11.3.2. Простой пример stat() Рассмотрим простую программу, которая отображает информацию из lstat() для каждого имени файла, переданного в аргументе. Она иллюстрирует, как использовать значения, возвращенные семейством функций stat(). 1: /* statsamp.с */ 2: 3: /* Для каждого имени файла,
Простой пример использования сигнала SIGURG
Простой пример использования сигнала SIGURG Теперь мы рассмотрим тривиальный пример отправки и получения внеполосных данных. В листинге 24.1[1] показана программа отправки этих данных.Листинг 24.1. Простая программа отправки внеполосных данных//oob/tcpsend01.c 1 #include "unp.h" 2 int 3 main(int
Простой пример использования функции select
Простой пример использования функции select Теперь мы переделаем код нашего получателя внеполосных данных и вместо сигнала SIGURG будем использовать функцию select. В листинге 24.3 показана принимающая программа.Листинг 24.3. Принимающая программа, в которой (ошибочно)
Простой пример
Простой пример Конечно, приведенный выше пример довольно сложен — ведь он написан на машинном языке и в шестнадцатиричном виде. Но его можно упростить, ведь отладчик в Windows XP поддерживает как ASCII-символы, так и язык «Ассемблера».Вот упрощением мы сейчас и займемся.
Пример простой хранимой процедуры
Пример простой хранимой процедуры Настало время создать первую хранимую процедуру и на ее примере изучить процесс создания хранимых процедур. Но для начала следует сказать несколько слов о том, как работать с хранимыми процедурами Дело в том, что своей славой
5.3.1. Учебный пример: SMTP, простой протокол передачи почты
5.3.1. Учебный пример: SMTP, простой протокол передачи почты В примере 5.7. иллюстрируется транзакция SMTP (Simple Mail Transfer Protocol — простой протокол передачи почты), который описан в спецификации RFC 2821. В данном примере строки, начинающиеся с С:, отправляются почтовым транспортным
5.3.1. Учебный пример: SMTP, простой протокол передачи почты
5.3.1. Учебный пример: SMTP, простой протокол передачи почты В примере 5.7. иллюстрируется транзакция SMTP (Simple Mail Transfer Protocol — простой протокол передачи почты), который описан в спецификации RFC 2821. В данном примере строки, начинающиеся с C:, отправляются почтовым транспортным
ПРИМЕР ПРОСТОЙ ПРОГРАММЫ НА ЯЗЫКЕ СИ
ПРИМЕР ПРОСТОЙ ПРОГРАММЫ НА ЯЗЫКЕ СИ Давайте рассмотрим простую программу на языке Си. Следует сразу сказать, что такой пример нужен нам лишь для выявления некоторых основных черт любой программы, написанной на языке Си. Далее мы дадим пояснения к каждой строке, но, перед
10.11. Пример шаблона функции
10.11. Пример шаблона функции В этом разделе приводится пример, показывающий, как можно определять и использовать шаблоны функций. Здесь определяется шаблон sort(), который затем применяется для сортировки элементов массива. Сам массив представлен шаблоном класса Array (см.
Пример 10-14. Простой цикл while
Пример 10-14. Простой цикл while #!/bin/bashvar0=0LIMIT=10while [ "$var0" -lt "$LIMIT" ]do echo -n "$var0 " # -n подавляет перевод строки. var0=`expr $var0 + 1` # допускается var0=$(($var0+1)).doneechoexit
Пример 10-27. Простой пример сравнения строк
Пример 10-27. Простой пример сравнения строк #!/bin/bash# match-string.sh: простое сравнение строкmatch_string (){ MATCH=0 NOMATCH=90 PARAMS=2 # Функция требует два входных аргумента. BAD_PARAMS=91 [ $# -eq $PARAMS ] || return $BAD_PARAMS case "$1" in "$2") return $MATCH;; * ) return $NOMATCH;; esac}a=oneb=twoc=threed=twomatch_string $a # неверное число
Пример 25-1. Простой массив
Пример 25-1. Простой массив #!/bin/basharea[11]=23area[13]=37area[51]=UFOs# Массивы не требуют, чтобы последовательность элементов в массиве была непрерывной.# Некоторые элементы массива могут оставаться неинициализированными.# "Дыркм" в массиве не являются ошибкой.echo -n "area[11] = "echo ${area[11]} #