QNX/UNIX: Анатомия параллелизма
Создание нового процесса
Созданию процессов (имеется в виду создание процесса из программного кода) посвящено столько описаний [1-9], что детальное рассмотрение этого вопроса было бы лишь пересказом. Поэтому мы ограничимся только беглым перечислением этих возможностей, тем более что в ходе обсуждения нас главным образом интересуют не сами процессы, а потоки, заключенные в адресных пространствах процессов.
Использование командного интерпретатора
Самый простой способ — запустить из программного кода дочернюю копию командного интерпретатора, которому затем передать команду запуска процесса. Для этого используется вызов:
int system(const char* command);где
— текстовая строка, содержащая команду, которую предполагается выполнить ровно в том виде, в котором мы вводим ее командному интерпретатору с консоли.commandПримечаниеФункция имеет еще одну специфическую форму вызова, когда в качестве
задаетсяcommand. По коду возврата это позволяет выяснить, присутствует ли (и доступен ли) командный интерпретатор в системе (возвращается 0, если интерпретатор доступен).NULLНа время выполнения вызова
вызывающий процесс приостанавливается. После завершения порожденного процесса функция возвращает код завершения вновь созданной копии интерпретатора (или -1, если сам интерпретатор не может быть выполнен), то есть младшие 8 бит возвращаемого значения содержат код завершения выполняемого процесса. Возврат вызоваsystem()может анализироваться макросомsystem(), определенным в файлеWEXITSTATUS(). Например:<sys/wait.h>
#include <sys/wait.h>
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;
}ПримечаниеЭта функция использует вызов
для загрузки новой копии командного интерпретатора, то есть «внутреннее устройство» должно быть в общем виде вам понятно. Особенностью QNX-реализации является то, чтоspawnlp()всегда использует вызовspawnlp(), независимо от конкретного вида интерпретатора, устанавливаемого переменной окружения SHELL (ksh, bash…). Это обеспечивает независимость поведения родительского приложения от конкретных установок системы, в которой это приложение выполняется./bin/shВызов
является не только простым, но и очень наглядным, делающим код легко читаемым. Программисты часто относятся к нему с пренебрежением [10], отмечая множество его недостатков. Однако в относительно простых случаях это может быть оптимальным решением, а недостатки не так и существенны:system()• Используя копию командного интерпретатора, вызов
может инициировать процесс, исполняющий и бинарную программу, и скрипт на языке самого командного интерпретатора (shell), а также программный код на интерпретирующих языках, таких как Perl, Tcl/Tk и др. Многие из рассматриваемых ниже «чисто программных» способов могут загружать и исполнять только бинарный исполняемый код (по крайней мере, без использования ими весьма громоздких искусственных и альтернативных возможностей).system()• Остановка родительского процесса в ожидании завершения порожденного также легко разрешается: просто запускайте дочерний процесс из отдельного потока [11]:
#include <pthread.h>
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);
...
}• Часто в качестве недостатка этого способа отмечают «автономность» и невозможность взаимодействия родительского и порожденного процессов.
Но для расширения возможностей взаимосвязи процессов можно прежде всего воспользоваться вызовом
(POSIX 1003.1a), являющимся в некотором роде эквивалентом, расширяющим возможностиpopen(). Возможностиsystem()часто упускаются из виду, так как в описаниях этот вызов относится не к области создания процессов, а к области программных каналов (pipe). Синтаксис этого вызова таков:popen()
FILE* popen(const char* command, const char* mode);где
— командная строка, как и уcommand;system()— режим создаваемого программного канала со стороны порождающего процесса: ввод (mode= «r») или вывод (mode= «w»). Любые другие значения, указанные дляmode, дают непредсказуемый результат.modeВ результате выполнения этой функции создается открытый файловый дескриптор канала (pipe), из которого породивший процесс может (
= «r») читать (стандартный поток вывода дочернего процессаmode) или в который может (STDOUT_FILENO= «w») писать (стандартный поток ввода дочернего процессаmode) стандартным образом, как это делается для типа FILE (в частности, с отработкой ситуации EOF).STDIN_FILENOРассмотрим следующий пример. Конечно, посимвольный ввод/вывод — это не лучшее решение, и здесь мы используем его только для простоты:
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 )