Олег Цилюрик - 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: Анатомия параллелизма - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
Системы реального времени не имеют права на такую роскошь: непредсказуемое рассредоточение копирующих операций по всему последующему выполнению, а поэтому и использование в них COW вообще, выглядит весьма сомнительно. В [4] мы описывали эксперименты в QNX, когда в код сервера, построенного на fork()
, была внесена «пассивная» строка, никак не используемая в программе, но определяющая весьма протяженную инициализированную область данных:
static long MEM[2500000];
При этом время реакции (ответа) сервера (затраты времени на выполнение fork()
) возросло в 50 раз и составило 0,12 сек на процессоре 400 МГц. Еще раз, но в другом контексте эта особенность будет обсуждена ниже при сравнении затрат производительности на создание процессов и потоков.
Дополнительным вызовом этого класса (для полноты обзора) является использование функции:
pid_t vfork(void);
В отличие от fork()
, этот вызов, впервые введенный в BSD UNIX, делает разделяемым для дочернего процесса адресное пространство родителя. Родительский процесс приостанавливается до тех пор, пока порожденный процесс не выполнит exec()
(загружая новый программный код дочернего процесса) или не завершится с помощью exit()
или аналогичных средств.
Функция vfork()
может быть реализована на аппаратных платформах с физической моделью памяти (без виртуализации памяти), a fork()
— не может (или реализуется с большими накладными расходами), так как требует создания абсолютной копии области адресного пространства, что в физической модели повлечет сложную динамическую модификацию адресных полей кода. Тем не менее (при некоторых кажущихся достоинствах) в BSD также подчеркивалось, что vfork()
таит в себе серьезную потенциальную опасность, поскольку позволяет одному процессу использовать или даже модифицировать адресное пространство другого, то есть фактически нарушает парадигму защищенных адресных пространств.
Запуск нового программного кода
Наконец, рассмотрим запуск на выполнение нового, отличного от родительского процесса программного кода, образ которого содержится в отдельном исполняемом файле в качестве дочернего процесса. Для этой цели в QNX существуют две группы функций: exec()
(их всего 8: execl()
, execle()
, execlp()
, execlpe()
, execv()
, execve()
, execvp()
, execvpe()
) и spawn()
(их 10: spawn()
, spawnl()
, spawnle()
, spawnlp()
, spawnlpe()
, spawnp()
, spawnv()
, spawnve()
, spawnvp()
, spawnvpe()
).
Это множество форм записи отличается синтаксисом, который определяет формат списка аргументов командной строки, полученного нами в качестве параметров функции main()
, передаваемых программе, а также некоторыми другими дополнительными деталями. Суффиксы в именах функций обозначают следующее:
• l
— список аргументов определяется через список параметров, заданных непосредственно в самом вызове. Этот список завершается нулевым аргументом NULL
;
• e
— окружение для процесса указывается посредством определения массива переменных окружения;
• p
— относительный путь поиска: если не указан полный путь к файлу программы (то есть имя файла не содержит разделителей « /
»), для его поиска используется переменная окружения PATH
;
• v
— список аргументов определяется через указатель на массив аргументов.
В нашу задачу не входит описание всех возможностей вызовов, тем более что они обстоятельно описаны в [1, 2, 5, 6], и мы будем использовать по тексту любую, более удобную для нас форму без дополнительных объяснений.
Большинство форм функции exec()
являются POSIX-совместимыми, а большая часть форм функции spawn()
представляет собой специфическое расширение QNX. Более того, даже для тех функций группы spawn()
, которые часто называют POSIX-совместимыми [1], техническая документация QNX определяет степень совместимости примерно в таких терминах: « …функция spawn() является функцией QNX Neutrino (основанной на POSIX 1003.1d черновом стандарте). »
Функции семейства exec()
, напротив, подменяют исполняемый код текущего процесса (не изменяя его идентификатор PID, права доступа, внешние ресурсы процесса, а также находящийся в том же адресном пространстве) исполняемым кодом из другого файла. Поэтому используются эти вызовы непосредственно после fork()
для замены копии вызывающего процесса новым (это классическая UNIX-технология использования).
Функции семейства spawn()
, напротив, порождают новый процесс (с новым идентификатором PID и в новом адресном пространстве). Все формы вызовов spawn() после подготовительной работы (иногда очень значительной) в конечном итоге ретранслируются в вызов базовой формы spawn()
[13] Тем не менее это вовсе не означает, что следует непосредственно использовать вызов spawn() , ведь он самый трудоемкий и чреват ошибками.
, который последним действием своего выполнения и посылает сообщение procnto
(менеджер процессов QNX, «территориально» объединенный с микроядром системы в одном файле).
Базовый вызов spawn()
определяется следующим образом:
#include
pid_t spawn(const char* path, int fd_count, const int fd_map[],
const struct inheritance* inherit, char* const argv[],
char* const envp[]);
где path
— полное имя исполняемого бинарного файла;
fd_count
— размерность следующего за ним массива fd_map
;
fd_map
— массив файловых дескрипторов, которые вы хотели бы унаследовать в дочернем процессе от родительского. Если fd_count
не равен 0 (то есть может иметь значения вплоть до константы OPEN_MAX
), то fd_map
должен содержать список из fd_count
файловых дескрипторов. Если же fd_count
равен 0, то дочерний процесс наследует все родительские дескрипторы, исключая те, которые созданы с флагом PD_CLOEXEC
функции fcntl()
;
inherit
— системная структура (см. системные определения) типа struct inheritance
, содержащая как минимум:
unsigned long flags
— один или более установленных бит:
SPAWN_CHECK_SCRIPT
— позволить spawn()
запускать требуемый командный интерпретатор, интерпретируя path
как скрипт (интерпретатор указан в первой строке скрипта path
);
SPAWN_SEARCH_PATH
— использовать переменную окружения PATH
для поиска выполняемого файла path
;
SPAWN_SETGROUP
— установить для дочернего процесса значение группы, специфицируемое членом (структуры) pgroup
. Если этот флаг не установлен, дочерний процесс будет частью текущей группы родительского процесса;
SPAWN_SETND
— запустить дочерний процесс на удаленном сетевом узле QNET, сам же удаленный узел специфицируется членом (структуры) nd
(см. команду удаленного запуска on
);
Интервал:
Закладка: