Основы программирования в Linux - Нейл Мэтью
Шрифт:
Интервал:
Закладка:
res = pthread_join(a_thread[lots_of_threads], &thread_result);
if (res == 0) {
printf("Picked up a threadn");
} else {
perror("pthread_join failed");
}
}
printf("All donen");
exit(EXIT_SUCCESS);
}
void* thread_function(void* arg) {
int my_number = (int)arg;
int rand_num;
printf("thread_function is running. Argument was %dn", my_number);
rand_num = 1+(int)(9.0*rand()/(RAND_MAX+1.0));
sleep(rand_num);
printf("Bye from %dn", my_number);
pthread_exit(NULL);
}
Резюме
В этой главе вы узнали, как создать несколько потоков исполнения внутри процесса, которые совместно используют глобальные переменные. Вы рассмотрели два способа управления — семафоры и мьютексы, применяемые потоками для доступа к важным фрагментам кода и данным. Далее вы увидели, как управлять атрибутами потоков и, в особенности, как можно отсоединить потоки от основного, не заставляя его ждать завершения созданных им потоков. После краткого обзора способов формирования в одном потоке запросов на отмену других потоков и вариантов управления такими запросами в потоке, получившем их, мы представили программу с множественными одновременно выполняющимися потоками.
Объем книги не позволяет обсудить все до единой функции и тонкости, связанные с потоками, но теперь у вас достаточно знаний для того, чтобы начать писать собственные программы, применяющие потоки, и изучать глубоко скрытые свойства потоков, читая страницы интерактивного справочного руководства.
Глава 13
Связь между процессами: каналы
В главе 11 вы видели очень простой способ пересылки сообщений между процессами с помощью сигналов. Вы формировали уведомляющие события, которые могли бы применяться для вызова ответа, но передаваемая информация была ограничена номером сигнала.
В этой главе вы познакомитесь с каналами, которые позволяют процессам обмениваться более полезной информацией. В конце этой главы вы примените свои вновь приобретенные знания для новой реализации программы, управляющей базой данных компакт-дисков, в виде клиент-серверного приложения.
В данной главе мы обсудим следующие темы:
□ определение канала;
□ каналы процессов;
□ вызовы каналов;
□ родительские и дочерние процессы;
□ именованные каналы — FIFO;
□ замечания, касающиеся клиент-серверных приложений.
Что такое канал?
Мы применяем термин "канал" для обозначения соединения потока данных одного процесса с другим. Обычно вы присоединяете или связываете каналом вывод одного процесса с вводом другого.
Большинство пользователей Linux уже знакомы с идеей конвейера, связывающего вместе команды оболочки так, что вывод одного процесса поставляет данные прямо во ввод другого. В случае команд оболочки это делается с помощью символа конвейера или канала, соединяющего команды следующим образом:
cmd1 | cmd2
Командная оболочка организует стандартный ввод и вывод двух команд так, что:
□ стандартный ввод cmd1 поступает с клавиатуры терминала;
□ стандартный вывод cmd1 поставляется cmd2 как ее стандартный ввод;
□ стандартный вывод cmd2 подсоединен к экрану терминала.
На самом деле командная оболочка заново соединила потоки стандартных ввода и вывода так, что потоки данных проходят с клавиатурного ввода через две команды и выводятся на экран. На рис. 13.1 приведено визуальное представление этого процесса.
Рис. 13.1
В этой главе вы увидите, как достичь этого эффекта в программе и как можно использовать каналы для связи многих процессов, что позволит создать простую клиент-серверную систему.
Каналы процессов
Возможно, простейший способ передачи данных между программами — применение функций popen и pclose. У них следующие прототипы:
#include <stdio.h>
FILE *popen(const char *command, const char *open_mode);
int pclose(FILE *stream_to_close);
popen
Функция popen позволяет программе запустить другую программу как новый процесс и либо передать ей данные, либо получить их из нее. Строка command — это имя программы для выполнения вместе с любыми параметрами, параметр open_mode должен быть "r" или "w".
Если open_mode — "r", вывод вызванной программы становится доступен вызывающей программе и может быть считан из возвращаемого функцией popen файлового потока FILE* с помощью обычных функций библиотеки stdio, предназначенных для чтения (например, fread). Но если open_mode — "w", программа может отправить данные вызванной команде с помощью вызова функции fwrite. Далее вызванная программа сможет читать данные из своего стандартного ввода. Обычно вызванная программа не знает, что она считывает данные из другого процесса; она просто читает свой поток стандартного ввода и воздействует на него.
Вызов функции popen должен задавать "r" или "w"; никакого другого значения стандартной реализацией popen не поддерживается. Это означает, что вы не можете вызвать другую программу и одновременно читать из нее и писать в нее. В случае сбоя popen возвращает пустой указатель. Если вы хотите создать двунаправленную связь с помощью каналов, стандартное решение — применить два канала: по одному для потока данных каждого направления.
pclose
Когда процесс, стартовавший с помощью popen, закончится, вы можете закрыть файловый поток, связанный с ним, с помощью функции pclose. Вызов pclose вернет управление, только когда процесс, запущенный с помощью popen, завершится. Если он все еще выполняется во время вызова pclose, вызов pclose будет ждать окончания процесса.
Функция pclose обычно возвращает код завершения процесса, чей файловый поток она закрывает. Если вызывающий процесс уже выполнил оператор wait перед вызовом pclose, статус завершения будет потерян, поскольку вызванный процесс закончен, и функция pclose вернет -1 с переменной errno, получившей значение ECHILD.
Выполните упражнение 13.1.
Упражнение 13.1. Чтение вывода внешней программыДавайте опробуем простой пример popen1.c с функциями popen и pclose. Вы будете применять в программе popen для доступа к информации из uname. uname — это команда, выводящая системную информацию, включая тип компьютера, имя ОС, версию и выпуск, а также сетевое имя машины.
Запустив программу, вы откроете канал к uname; сделаете его читаемым и зададите read_fp, как указатель на вывод. В конце канал, на который указывает read_fp, закрывается.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main() {
FILE *read_fp;
char buffer[BUFSIZ +1];
int chars_read;
memset(buffer, ' ', sizeof(buffer));
read_fp = popen("uname -a", "r");
if (read_fp ! = NULL) {
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
if (chars_read > 0) {
printf("Output was:-n%sn", buffer);
}
pclose(read_fp);
exit(EXIT_SUCCESS);
}
exit(EXIT_FAILURE);
}
Когда вы выполните программу, то должны получить вывод, похожий на следующий (полученный на одной из машин авторов):
$ ./popen1
Output was:-
Linux suse103 2.6.20.2-2-default #1 SMP Fri Mar 9 21:54:10 UTC 2001 i686 i686 i386 GNU/Linux
Как это работает
Программа применяет функцию popen для вызова команды uname с параметром -а. Затем она использует возвращенный файловый поток для чтения данных, до BUFSIZ символов (как задано в директиве #define из файла stdio.h), и затем выводит их на экран. Поскольку вы перехватываете вывод команды uname внутри программы, его можно обрабатывать.
Отправка вывода в popen
Теперь, когда вы рассмотрели пример захвата вывода из внешней программы, давайте познакомимся с отправкой вывода во внешнюю программу. В упражнении 13.2 показана программа popen2.c, передающая по каналу данные другой программе. В этом примере будет использована команда od (от англ. octal dump — восьмеричный дамп).
Упражнение 13.2. Пересылка вывода в другую программуВзглянув на следующий программный код, вы увидите, что он очень похож на предыдущий пример, за исключением того, что вы пишете данные в канал вместо чтения данных из него. Далее приведена программа popen2.c.