Простой пример функции io_write()
Простой пример функции io_write()
Это был простой пример функции io_read(); давайте теперь перейдем к функции io_write(). Основной камень преткновения, связанный с io_write(), — получить доступ к данным. Поскольку библиотека администратора ресурсов считывает лишь незначительную часть сообщения от клиента, переданные клиентом данные (они идут сразу после заголовка _IO_WRITE) могут быть приняты функцией io_write() только частично. Простой пример — представьте себе клиента, записывающего один мегабайт. Библиотекой администратора ресурсов будут считаны только заголовок сообщения и несколько байт данных. Остальная часть мегабайта остается по-прежнему доступной на клиентской стороне — администратор ресурсов при желании может к ней обращаться.
Реально рассмотрения заслуживают только два случая:
• все содержимое сообщения клиентской функции write() было считано библиотекой администратора ресурсов полностью; или
• этого не произошло.
Судьбоносное решение, однако, состоит в ответе на следующий вопрос: «Какие проблемы сопряжены с попыткой сохранить полученную с первым сообщением часть данных?» Ответ такой: овчинка не стоит выделки. Тому есть ряд причин:
• обмен сообщениями (операции копирования на уровне ядра) выполняется очень быстро;
• проверка, получены ли данные целиком или частично, влечет определенные накладные расходы;
• дополнительные накладные расходы связаны с попыткой «сохранения» первой, уже прибывшей, части данных, в свете того факта, что ожидаются дополнительные.
По-моему, первые два пункта говорят сами за себя. Третий же пункт заслуживает пояснения. Давайте предположим, что клиент переслал большую порцию данных, и мы приняли-таки решение о том, что было бы неплохо попробовать сохранить ту часть данных, которая уже получена. К сожалению, эта часть оказалось очень небольшой. Это означает, что вместо одного непрерывного массива байт у нас теперь будет большой кусок и маленький «довесок». Иными словами, только ради этого маленького кусочка нам придется значительно усложнять работу с данными, что может неприятно сказаться на эффективности кода. Это потенциальная головная боль, не делайте так!
Реальным ответом на поставленный вопрос будет просто заново считать данные в заранее подготовленные буферы. В нашем простом примере функции io_write() я буду просто каждый раз выделять буфер при помощи malloc(), считывать в него данные, а затем освобождать его функцией free(). Разумеется, есть и куда более эффективные способы выделения буферов и управления ими!
Еще один тонкий момент в этом примере функции io_write() — это обработка модификатора _IO_XTYPE_OFFSET (и связанных с этим данных; здесь она выполняется несколько иначе, чем в примере io_read()).
/*
* io_write1.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/neutrino.h>
#include <sys/iofunc.h>
void process_data(int offset, void *buffer, int nbytes) {
// Сделать что-нибудь с данными
}
int io_write(resmgr_context_t *ctp, io_write_t *msg,
iofunc_ocb_t *ocb) {
int sts;
int nbytes;
int off;
int start_data_offset;
int xtype;
char *buffer;
struct _xtype_offset *xoffset;
// Проверить, открыто ли устройство на запись
if ((sts =
iofunc_write_verify(ctp, msg, ocb, NULL)) != EOK) {
return (sts);
}
// 1) Проверить и обработать переопределение
XTYPE xtype = msg->i.xtype & _IO_XTYPE_MASK;
if (xtype == _IO_XTYPE_OFFSET) {
xoffset = (struct _xtype_offset*)(&msg->i + 1);
start_data_offset = sizeof(msg->i) + sizeof(*xoffset);
off = xoffset->offset;
} else if (xtype == _IO_XTYPE_NONE) {
off = ocb->offset;
start_data_offset = sizeof(msg->i);
} else {
// Неизвестный тип; игнорировать
return (ENOSYS);
}
// 2) Выделить достаточно большой буфер для данных
nbytes = msg->i.nbytes;
if ((buffer = malloc(nbytes)) == NULL) {
return (ENOMEM);
}
// 3) Считать данные от клиента (возможно, повторно)
if (resmgr_msgread(ctp, buffer, nbytes,
start_data_offset) == -1) {
free(buffer);
return (errno);
}
// 4) Сделать что-нибудь с данными
process_data(off, buffer, nbytes);
// 5) Освободить память буфера
free(buffer);
// 6) Установить, сколько байт должна возвращать
// клиентская функция «write»
_IO_SET_WRITE_NBYTES(ctp, nbytes);
// 7) Если данные записаны, обновить структуры
// данных POSIX и смещение OCB
if (nbytes) {
ocb->attr->flags |=
IOFUNC_ATTR_MTIME | IОFUNC_ATTR_DIRTY_TIME;
if (xtype == _IO_XTYPE_NONE) {
ocb->offset += nbytes;
}
}
// 8) Пусть библиотека сама ответит, что все в порядке
return (EOK);
}
Как вы видите, некоторые начальные действия идентичны таковым из примера функции io_read() — функция iofunc_write_verify() аналогична функции iofunc_read_verify(), и проверка переопределения xtype выполняется точно также.
Этап 1
Здесь мы выполняем обработку переопределения xtype, в значительной степени аналогичную примеру с io_read() — за исключением того, что смещение не сохраняется в поле структуры входящего сообщения. Причина этого состоит в том, что обычной практикой для определения начального адреса поступающих от клиента данных является использование размера структуры входящего сообщения. Мы предпринимаем дополнительные усилия, чтобы удостовериться, что смещение начала данных (doffset) в коде обработки xtype является корректным.
Этап 2
Здесь мы выделяем буфер, достаточный для размещения в нем данных. Число байт, которые клиент собирается записать, представлено нам в поле nbytes объединения msg, оно заполняется автоматически Си-библиотекой клиента в коде функции write(). Отметим, что у нас недостаточно памяти для обработки запроса malloc(), мы возвращаем клиенту ENOMEM, чтобы он знал, почему его запрос потерпел неудачу.
Этап 3
Здесь мы применяем вспомогательную функцию resmgr_msgread() для считывания всего объема данных от клиента непосредственно в только что выделенный для этого буфер. В большинстве случаев здесь вполне сошла бы функция MsgRead(), но в случаях, когда сообщение является частью составного сообщения, функция resmgr_msgread() выполни для нас еще и соответствующие «магические» действия (о том, почему это надо, см. раздел «Составные сообщения»). Параметры функции resmgr_msgread() довольно очевидны: мы передаем ей указатель на внутренний контекстный блок (ctp), буфер, в который мы хотим поместить данные (buffer), и число байт, которые мы хотим считать (поле nbytes объединения msg). Последний параметр — это смещение, которое мы вычислили ранее, на этапе 1. Смещение реально позволяет пропустить заголовок, помещенный туда функцией write() клиентской Си-библиотеки, и сразу перейти к данным. Здесь возникает два интересных момента:
• мы могли бы взять произвольное смещение, чтобы считывать любые фрагменты данных в любом порядке, как нам заблагорассудится;
• мы могли бы использовать функцию resmgr_msgreadv() (обратите внимание на символ «V» в названии) для считывания данных от клиента в вектор ввода/вывода, возможно, описывающий несколько разных буферов, подобно тому, как мы делали с буферами кэша при обсуждении файловых систем в главе «Обмен сообщениями».
Этап 4
Здесь вы можете делать с данными все, что вашей душе угодно — я, например, вызвал некую условную функцию process_data() и передал ей буфер и его размер.
Этап 5
Критически важный этап! Упустить его из виду очень просто, но это неизбежно приведет к «утечкам памяти». Обратите также внимание, как мы позаботились об освобождении памяти в случае неудачи на этапе 3.
Этап 6
Здесь мы используем макрос _IO_SET_WRITE_NBYTES() для сохранения числа записанных байт, которое затем будет передано назад клиенту в качестве значения, возвращаемого функцией write(). Важно отметить, что вы должны возвратить фактическое число байт! От этого зависит судьба клиента.
Этап 7
Пришло время навести лоск для функций stat(), lseek() и последующих write(), аналогично тому, как мы это делали в нашей io_read() (и опять мы изменяем смещение в ocb только в том случае, если это не сообщение типа _IO_XTYPE_OFFSET). Однако, поскольку мы выполняем запись в устройство, мы используем при этом константу IOFUNC_ATTR_MTIME вместо константы IOFUNC_ATTR_ATIME. Флаг MTIME означает «время модификации» (modification time) — функция write() определенно его изменяет.
Этап 8
Последний этап прост: мы возвращаем константу EOK, которая сообщает библиотеке администратора ресурсов, что она должна ответить клиенту. Здесь наша работа закончена. Библиотека администратора ресурсов использует в ответе данные о числе записанных байт, которые мы сохранили с помощью макроса IO_SET_WRITE_NBYTES(), и разблокирует клиента. Клиентская функция write() возвратит число байт, записанное нашим устройством.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Простой пример
Простой пример Начнем с простого примера: наложим переход на изменение фона ссылки. Когда пользователь будет наводить на ссылку, цвет ее фона будет меняться, и мы применим переход, чтобы сделать это изменение плавным. Такого эффекта раньше можно было добиться
Простой пример функции io_read()
Простой пример функции io_read() Чтобы проиллюстрировать, как ваш администратор ресурса мог бы возвращать клиенту данные, рассмотрим простейший администратор ресурса, который всегда возвращает одну и ту же строковую константу «Здравствуй, мир! ». Даже в таком простом случае
Простой пример функции 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]} #