Олег Цилюрик - QNX/UNIX: Анатомия параллелизма
- Название:QNX/UNIX: Анатомия параллелизма
- Автор:
- Жанр:
- Издательство:Символ-Плюс
- Год:2006
- Город:Санкт-Петербург
- ISBN:5-93286-088-Х
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Олег Цилюрик - QNX/UNIX: Анатомия параллелизма краткое содержание
Книга адресована программистам, работающим в самых разнообразных ОС UNIX. Авторы предлагают шире взглянуть на возможности параллельной организации вычислительного процесса в традиционном программировании. Особый акцент делается на потоках (threads), а именно на тех возможностях и сложностях, которые были привнесены в технику параллельных вычислений этой относительно новой парадигмой программирования. На примерах реальных кодов показываются приемы и преимущества параллельной организации вычислительного процесса. Некоторые из результатов испытаний тестовых примеров будут большим сюрпризом даже для самых бывалых программистов. Тем не менее излагаемые техники вполне доступны и начинающим программистам: для изучения материала требуется базовое знание языка программирования C/C++ и некоторое понимание «устройства» современных многозадачных ОС UNIX.
В качестве «испытательной площадки» для тестовых фрагментов выбрана ОСРВ QNX, что позволило с единой точки зрения взглянуть как на специфические механизмы микроядерной архитектуры QNX, так и на универсальные механизмы POSIX. В этом качестве книга может быть интересна и тем, кто не использует (и не планирует никогда использовать) ОС QNX: программистам в Linux, FreeBSD, NetBSD, Solaris и других традиционных ОС UNIX.
QNX/UNIX: Анатомия параллелизма - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
• Вся система построена на тщательно проработанной в теории (и редко используемой при построении реальных ОС) концепции - коммутации сообщений. Ядро (точнее «микроядро») операционной системы при таком подходе выступает в качестве компактного коммутатора сообщений между взаимодействующими программными компонентами. При этом взаимодействующие компоненты выступают в качестве клиента, запрашивающего услугу (ресурс), и сервера, обеспечивающего эту услугу (обслуживающего ресурс).
• Большинство системных вызовов API (в том числе все «привычные» POSIX-вызовы: open()
, read()
, write()
, seek()
, close()
…) реально посылаются обслуживающему данный ресурс сервису (например, в файловую систему типа FAT32 — fs-dos
) в виде сообщений уровня микроядра. Код сообщения при этом определяет тип операции (например, open()
), а последующее тело сообщения — конкретные параметры запроса, зависящие от типа операции (параметры запроса пакуются в тело сообщения).
• Раз эта схема столь универсальна, единообразна и не зависит от конкретной природы ресурса, на котором обеспечивается обслуживание, то разработчики QNX предоставляют некоторый шаблон сервера, в котором на месте обработчиков стандартных POSIX-запросов находятся пустые программные заглушки. Этот шаблон и служит базовым элементом построения разнообразных серверов услуг, называемых при выполнении в такой технике «менеджерами ресурса».
• При запуске программа менеджера ресурса регистрирует свое имя (точнее имя управляемого ею ресурса) в пространстве имен файловой системы QNX (обычно в каталоге /dev
, но это может быть любое место файловой системы). Теперь можно обращаться с запросами к данному менеджеру так же, как и к любому реальному файлу в файловой системе.
• Программисту, пишущему свой драйвер услуги, ресурса, устройства или псевдоустройства, остается только переопределить программное наполнение тех программных заглушек, которые ответственны за интересующие его вызовы (например, open()
, read()
, close()
), никак не затрагивая вызовы, не обеспечиваемые этим ресурсом (например, write()
, seek()
и др.).
В наши цели не входит детальное обсуждение техники написания менеджеров ресурсов (этому посвящено специальное исчерпывающее руководство в составе технической документации QNX объемом более 80 страниц [42] В Интернете доступен прекрасный перевод этого документа, выполненный Владимиром Зайцевым и отредактированный к публикации Сергеем Малышевым: http://qnxclub.net/files/articles/resmgr/resmgr.pdf.gz .
). Поэтому, как и ранее с динамическим пулом потоков, начнем с примера. Приведем простейший код менеджера ресурса, который использовался нами для тестирования наследования приоритетов в QNX ( файл prior.cc ):
#include
#include
#include
#include
#include
#include
#include
#include
#include
// обработчик запроса от клиента read(),
// возвращающий текущий приоритет обслуживания
static int prior_read(resmgr_context_t *ctp, io_read_t *msg,
RESMGR_OCB_T *ocb) {
static bool odd = true;
int status = iofunc_read_verify(ctp, msg, ocb, NULL);
if (status != EOK) return status;
if (msg->i.xtype & _IO_XTYPE_MASK != _ID_XTYPE_NONE)
return ENOSYS;
if (odd) {
struct sched_param param;
sched_getparam(0, ¶m);
static char rbuf[4];
sprintf(rbuf, "%d\n", param.sched_curpriority);
MsgReply(ctp->rcvid, strlen(rbuf) + 1, rbuf, strlen(rbuf) + 1);
} else MsgReply(ctp->rcvid, EOK, NULL, 0);
odd = !odd;
return _RESMGR_NOREPLY;
}
// главная программа запуска менеджера
main(int argc, char **argv) {
resmgr_attr_t resmgr_attr;
dispatch_t *dpp;
dispatch_context_t *ctp;
int id;
// инициализация интерфейса диспетчеризации
if ((dpp = dispatch_create()) == NULL)
perror("allocate dispatch"), exit(EXIT_FAILURE);
// инициализация атрибутов менеджера
memset(&resmgr_attr, 0, sizeof resmgr_attr);
resmgr_attr.nparts_max = 1;
resmgr_attr.msg_max_size = 2048;
// инициализация таблиц функций обработчиков
static resmgr_connect_funcs_t connect_funcs;
static resmgr_io_funcs_t io_funcs;
iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &connect_funcs,
_RESMGR_IO_NFUNCS, &io_funcs);
// здесь нами дописан всего один обработчик - операции read,
// все остальное делается менеджером по умолчанию!
io_funcs.read = prior_read;
// инициализация атрибутной структуры, используемой
// устройством.
static iofunc_attr_t attr;
iofunc_attr_init(&attr, S_IFNAM | 0666, 0, 0);
// здесь создается путевое имя для менеджера
id = resmgr_attach(dpp, &resmgr_attr, "/dev/prior",
_FTYPE_ANY, 0, &connect_funcs, &io_funcs, &attr);
if (id == -1)
perror("attach name"), exit(EXIT_FAILURE);
ctp = dispatch_context_alloc(dpp);
// старт менеджера как бесконечный цикл ожидания
// поступающих сообщений для диспетчеризации:
while (true) {
if ((ctp = dispatch_block(ctp)) == NULL)
perror("block error"), exit(EXIT_FAILURE);
dispatch_handler(ctp);
}
}
Здесь использован простейший однопоточный шаблон написания менеджера. Менеджер отрабатывает только одну команду read()
(т.e. отрабатывает нестандартно; в целевом коде все остальные команды, например open()
, он отрабатывает по умолчанию). По команде read()
менеджер: а) возвращает в виде текстовой строки, завершающейся переводом строки, текущий приоритет (помните, что в QNX приоритеты «плавают»?), на котором он обрабатывает запрос, и б) делает это через один запрос, в оставшиеся разы создавая на всякий случай (почему «на всякий», сейчас станет понятно) ситуацию EOF (конца файла). Выполним несколько команд:
# prior &
# ls -l /dev/pr*
nrw-rw-rw- 1 root root 0 Dec 18 17:13 /dev/prior
Все соответствует нашим ожиданиям: менеджер ресурса запущен, он зарегистрировал в пространстве имен свое имя /dev/prior
, по которому мы можем к нему обращаться. Теперь выполним обращения к нашему... «устройству». Для этого мы сознательно не станем пользоваться каким-либо специальным клиентом, запрашивающим наш созданный сервис, а воспользуемся самыми заурядными командами UNIX, которые ничего не подозревают о существовании нового сервиса:
# cat /dev/prior
10
# nice -n-5 cat /dev/prior
15
# nice -n-19 cat /dev/prior
29
Вот здесь и проявляется исключительная мощь техники написания менеджера ресурса: созданная минимальными средствами серверная служба «камуфлирует» специфичный QNX-механизм передачи сообщений микроядра под стандартные POSIX-запросы к файловой системе ( open()
, read()
и т.д.), и стандартные команды UNIX «не видят» отличий новой серверной службы от стандартных файлов (устройств) UNIX. Вот для достижения такой полной совместимости с «привычками» команд UNIX и созданы «на всякий случай» те особенности формата, возвращаемого запросами read()
, о которых упоминалось выше.
Интервал:
Закладка: