Олег Цилюрик - 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: Анатомия параллелизма - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
• RID и EUID — реальный и эффективный идентификаторы пользователя. Эффективный идентификатор служит для определения прав доступа процесса к системным ресурсам (в первую очередь к файловым системам). Обычно RID и EUID совпадают, но установка флага SUID для исполняемого файла процесса позволяет расширить полномочия процесса.
• RGID и EGID — реальный и эффективный идентификаторы группы пользователей. Как и в случае идентификаторов пользователя, EGID не совпадает с RGID, если установлен флаг SGID для исполняемого файла процесса.
Часто в качестве атрибутов процесса называют и приоритет выполнения. Однако приоритет является атрибутом не процесса (процесс — это статическая субстанция, контейнер), а потока, но если поток единственный (главный, порожденный функцией main()
), его приоритет и есть то, что понимается под «приоритетом процесса».
Создание нового процесса
Созданию процессов (имеется в виду создание процесса из программного кода) посвящено столько описаний [1-9], что детальное рассмотрение этого вопроса было бы лишь пересказом. Поэтому мы ограничимся только беглым перечислением этих возможностей, тем более что в ходе обсуждения нас главным образом интересуют не сами процессы, а потоки, заключенные в адресных пространствах процессов.
Использование командного интерпретатора
Самый простой способ — запустить из программного кода дочернюю копию командного интерпретатора, которому затем передать команду запуска процесса. Для этого используется вызов:
int system(const char* command);
где command
— текстовая строка, содержащая команду, которую предполагается выполнить ровно в том виде, в котором мы вводим ее командному интерпретатору с консоли.
Функция имеет еще одну специфическую форму вызова, когда в качестве command
задается NULL
. По коду возврата это позволяет выяснить, присутствует ли (и доступен ли) командный интерпретатор в системе (возвращается 0, если интерпретатор доступен).
На время выполнения вызова system()
вызывающий процесс приостанавливается. После завершения порожденного процесса функция возвращает код завершения вновь созданной копии интерпретатора (или -1, если сам интерпретатор не может быть выполнен), то есть младшие 8 бит возвращаемого значения содержат код завершения выполняемого процесса. Возврат вызова system()
может анализироваться макросом WEXITSTATUS()
, определенным в файле . Например:
#include
int main(void) {
int rc = system("ls");
if (rc == -1) cout << "shell could not be run" << endl;
else
cout << "result of running command is " << WEXITSTATUS(rc) << endl;
return EXIT_SUCCESS;
}
Эта функция использует вызов spawnlp()
для загрузки новой копии командного интерпретатора, то есть «внутреннее устройство» должно быть в общем виде вам понятно. Особенностью QNX-реализации является то, что spawnlp()
всегда использует вызов /bin/sh
, независимо от конкретного вида интерпретатора, устанавливаемого переменной окружения SHELL (ksh, bash…). Это обеспечивает независимость поведения родительского приложения от конкретных установок системы, в которой это приложение выполняется.
Вызов system()
является не только простым, но и очень наглядным, делающим код легко читаемым. Программисты часто относятся к нему с пренебрежением [10] Здесь многое зависит от расстановки приоритетов. Если вы хотите, чтобы всякий, читающий ваш код, тут же воскликнул: «Ну и крутой же парень написал такое!», заведомо используйте spawn() . При желании сделать код максимально элегантным используйте fork() , а если ставится задача хорошей читаемости и ясности кода, то очень часто достаточно и system() .
, отмечая множество его недостатков. Однако в относительно простых случаях это может быть оптимальным решением, а недостатки не так и существенны:
• Используя копию командного интерпретатора, вызов system()
может инициировать процесс, исполняющий и бинарную программу, и скрипт на языке самого командного интерпретатора (shell), а также программный код на интерпретирующих языках, таких как Perl, Tcl/Tk и др. Многие из рассматриваемых ниже «чисто программных» способов могут загружать и исполнять только бинарный исполняемый код (по крайней мере, без использования ими весьма громоздких искусственных и альтернативных возможностей).
• Остановка родительского процесса в ожидании завершения порожденного также легко разрешается: просто запускайте дочерний процесс из отдельного потока [11] Детали создания потока и и частности передачи ему параметра обстоятельно рассматриваются далее.
:
#include
void* process(void* command) {
system((char*)command);
delete command;
return NULL;
}
int main(int argc, char *argv[]) {
...
char* comstr = "ls -l";
pthread_create(NULL, NULL, strdup(comstr), &process);
...
}
• Часто в качестве недостатка этого способа отмечают «автономность» и невозможность взаимодействия родительского и порожденного процессов.
Но для расширения возможностей взаимосвязи процессов можно прежде всего воспользоваться вызовом popen()
(POSIX 1003.1a), являющимся в некотором роде эквивалентом, расширяющим возможности system()
. Возможности popen()
часто упускаются из виду, так как в описаниях этот вызов относится не к области создания процессов, а к области программных каналов (pipe). Синтаксис этого вызова таков:
FILE* popen(const char* command, const char* mode);
где command
— командная строка, как и у system()
; mode
— режим создаваемого программного канала со стороны порождающего процесса: ввод ( mode
= «r») или вывод ( mode
= «w»). Любые другие значения, указанные для mode
, дают непредсказуемый результат.
В результате выполнения этой функции создается открытый файловый дескриптор канала (pipe), из которого породивший процесс может ( mode
= «r») читать (стандартный поток вывода дочернего процесса STDOUT_FILENO
) или в который может ( mode
= «w») писать (стандартный поток ввода дочернего процесса STDIN_FILENO
) стандартным образом, как это делается для типа FILE (в частности, с отработкой ситуации EOF).
Рассмотрим следующий пример. Конечно, посимвольный ввод/вывод — это не лучшее решение, и здесь мы используем его только для простоты:
int main(int argc, char** argv) {
FILE* f = popen("ls -l", "r");
if (f == NULL) perror("popen"), exit(EXIT_FAILURE);
char c;
while((с = fgetc(f)) != EOF )
cout << (islower(с) ? toupper(с) : c);
pclose(f);
return EXIT_SUCCESS;
}
Новый процесс выполняется с тем же окружением, что и родительский. Процесс, указанный в команде, запускается примерно следующим эквивалентом:
Читать дальшеИнтервал:
Закладка: