Роб Кёртен - Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform
- Название:Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform
- Автор:
- Жанр:
- Издательство:Петрополис
- Год:2001
- Город:Санкт-Петербург
- ISBN:5-94656-025-9
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Роб Кёртен - Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform краткое содержание
Книга "Введение в QNX/Neutrino 2» откроет перед вами в мельчайших подробностях все секреты ОСРВ нового поколения от компании QNX Software Systems Ltd (QSSL) — QNX/Neutrino 2. Книга написана в непринужденной манере, легким для чтения и понимания стилем, и поможет любому, от начинающих программистов до опытных системотехников, получить необходимые начальные знания для проектирования надежных систем реального времени, от встраиваемых управляющих приложений до распределенных сетевых вычислительных систем
В книге подробно описаны основные составляющие ОС QNX/Neutrino и их взаимосвязи. В частности, уделено особое внимание следующим темам:
• обмен сообщениями: принципы функционирования и основы применения;
• процессы и потоки: базовые концепции, предостережения и рекомендации;
• таймеры: организация периодических событий в программах;
• администраторы ресурсов: все, что относится к программированию драйверов устройств;
• прерывания: рекомендации по эффективной обработке.
В книге представлено множество проверенных примеров кода, подробных разъяснений и рисунков, которые помогут вам детально вникнуть в и излагаемый материал. Примеры кода и обновления к ним также можно найти на веб-сайте автора данной книги, www.parse.com.
Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
/*
* io_write1.c
*/
#include
#include
#include
#include
#include
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 выполняется точно также.
Здесь мы выполняем обработку переопределения xtype, в значительной степени аналогичную примеру с io_read() — за исключением того, что смещение не сохраняется в поле структуры входящего сообщения. Причина этого состоит в том, что обычной практикой для определения начального адреса поступающих от клиента данных является использование размера структуры входящего сообщения. Мы предпринимаем дополнительные усилия, чтобы удостовериться, что смещение начала данных ( doffset ) в коде обработки xtype является корректным.
Здесь мы выделяем буфер, достаточный для размещения в нем данных. Число байт, которые клиент собирается записать, представлено нам в поле nbytes объединения msg , оно заполняется автоматически Си-библиотекой клиента в коде функции write() . Отметим, что у нас недостаточно памяти для обработки запроса malloc() , мы возвращаем клиенту ENOMEM, чтобы он знал, почему его запрос потерпел неудачу.
Здесь мы применяем вспомогательную функцию resmgr_msgread() для считывания всего объема данных от клиента непосредственно в только что выделенный для этого буфер. В большинстве случаев здесь вполне сошла бы функция MsgRead() , но в случаях, когда сообщение является частью составного сообщения, функция resmgr_msgread() выполни для нас еще и соответствующие «магические» действия (о том, почему это надо, см. раздел «Составные сообщения»). Параметры функции resmgr_msgread() довольно очевидны: мы передаем ей указатель на внутренний контекстный блок ( ctp ), буфер, в который мы хотим поместить данные ( buffer ), и число байт, которые мы хотим считать (поле nbytes объединения msg ). Последний параметр — это смещение, которое мы вычислили ранее, на этапе 1. Смещение реально позволяет пропустить заголовок, помещенный туда функцией write() клиентской Си-библиотеки, и сразу перейти к данным. Здесь возникает два интересных момента:
• мы могли бы взять произвольное смещение, чтобы считывать любые фрагменты данных в любом порядке, как нам заблагорассудится;
• мы могли бы использовать функцию resmgr_msgreadv() (обратите внимание на символ «V» в названии) для считывания данных от клиента в вектор ввода/вывода, возможно, описывающий несколько разных буферов, подобно тому, как мы делали с буферами кэша при обсуждении файловых систем в главе «Обмен сообщениями».
Здесь вы можете делать с данными все, что вашей душе угодно — я, например, вызвал некую условную функцию process_data () и передал ей буфер и его размер.
Критически важный этап! Упустить его из виду очень просто, но это неизбежно приведет к «утечкам памяти». Обратите также внимание, как мы позаботились об освобождении памяти в случае неудачи на этапе 3.
Здесь мы используем макрос _IO_SET_WRITE_NBYTES() для сохранения числа записанных байт, которое затем будет передано назад клиенту в качестве значения, возвращаемого функцией write() . Важно отметить, что вы должны возвратить фактическое число байт! От этого зависит судьба клиента.
Пришло время навести лоск для функций stat() , lseek() и последующих write() , аналогично тому, как мы это делали в нашей io_read() (и опять мы изменяем смещение в ocb только в том случае, если это не сообщение типа _IO_XTYPE_OFFSET). Однако, поскольку мы выполняем запись в устройство, мы используем при этом константу IOFUNC_ATTR_MTIME вместо константы IOFUNC_ATTR_ATIME. Флаг MTIME означает «время модификации» (modification time) — функция write() определенно его изменяет.
Последний этап прост: мы возвращаем константу EOK, которая сообщает библиотеке администратора ресурсов, что она должна ответить клиенту. Здесь наша работа закончена. Библиотека администратора ресурсов использует в ответе данные о числе записанных байт, которые мы сохранили с помощью макроса IO_SET_WRITE_NBYTES() , и разблокирует клиента. Клиентская функция write() возвратит число байт, записанное нашим устройством.
Простой пример функции io_devctl()
Клиентский вызов devctl() формально определен так:
#include
#include
#include
int devctl(int fd , int dcmd , void * dev_data_ptr ,
size_t nbytes , int * dev_info_ptr );
Прежде чем рассматривать эту функцию с позиций администратора ресурсов, надо сначала понять, что это за зверь. Функция devctl() применяется для «нестандартных» и «управляющих» операций. Например, вы можете записывать данные в звуковую плату (реальные оцифрованные звуковые фрагменты, которые звуковая плата должна будет конвертировать в аналоговый аудиосигнал) и принять решение об изменении числа каналов от одного (моно) до двух (стерео) или об изменении частоты дискретизации данных от стандарта CD (44.1 кГц) к стандарту DAT (48 кГц). Такие вещи было бы правильно делать при помощи функции devctl() . При написании администратора ресурсов вы можете решить, что вам вообще не нужны никакие devctl() , и что всю необходимую функциональность можно свести к стандартным функциям read() и write() . С другой стороны, вы можете захотеть использовать как вызовы devctl() наряду с вызовами read() и write() , так и только devctl() — это будет зависеть от вашего устройства.
Читать дальшеИнтервал:
Закладка: