33 if ((rcount = read(pipefd[0], buf, BUFSIZ)) != wcount) {
34 fprintf(stderr, "%s: read failed: %s\n", argv[0],
35 strerror(errno));
36 exit(1);
37 }
38
39 buf[rcount] = '\0';
40
41 printf("Read <%s> from pipe\n", buf);
42 (void)close(pipefd[0]);
43 (void)close(pipefd[1]);
44
45 return 0;
46 }
Строки 11–15 объявляют локальные переменные; наибольший интерес представляет mesg
, который представляет текст, проходящий по каналу.
Строки 17–21 создают канал с проверкой ошибок; строки 23–24 выводят значения новых дескрипторов файлов (просто для подтверждения, что они не равны 0, 1 или 2)
В строке 26 получают длину сообщения для использования с write()
. Строки 27–31 записывают сообщение в канал, снова с проверкой ошибок.
Строки 33–37 считывают содержимое канала, опять с проверкой ошибок. Строка 39 предоставляет завершающий нулевой байт, так что прочитанные данные могут использоваться в качестве обычной строки. Строка 41 выводит данные, а строки 42–43 закрывают оба конца канала. Вот что происходит при запуске программы:
$ ch09-pipedemo
Read end = fd 3, write end = fd 4
Read
Эта программа не делает ничего полезного, но она демонстрирует основы. Обратите внимание, что нет вызовов open()
или creat()
и что программа не использует три своих унаследованных дескриптора. Тем не менее, write()
и read()
завершаются успешно, показывая, что дескрипторы файлов действительны и что данные, поступающие в канал, действительно выходят из него.[95] Конечно, будь сообщение слишком большим, наша программа не работала бы. Это происходит из-за того, что размер (памяти) каналов ограничен, факт, который мы обсудим в следующем разделе.
Подобно другим дескрипторам файлов, дескрипторы для каналов наследуются порожденным процессом после fork
, и если они не закрываются, все еще доступны после exec
. Вскоре мы увидим, как использовать это обстоятельство и сделать с каналами что-то интересное.
9.3.1.2. Буферирование каналов
Каналы
Когда канал полон, система автоматически write()
. Когда канал освобождается, система копирует данные в канал, а затем позволяет системному вызову write()
вернуться к производителю.
Подобным же образом, если канал пустой, потребитель блокируется в read()
до тех пор, пока в канале не появятся данные для чтения. (Блокирующее поведение можно отключить; это обсуждается в разделе 9.4.3.4 «Неблокирующий ввод/вывод для каналов и очередей FIFO».)
Когда производитель вызывает на записывающем конце канала close()
, потребитель может успешно прочесть любые данные, все еще находящиеся в канале. После этого дальнейшие вызовы read()
возвращают 0, указывая на конец файла.
Напротив, если потребитель закрывает читаемый конец, write()
на записываемом конце завершается неудачей. В частности, ядро посылает производителю сигнал «нарушенный канал», действием по умолчанию для которого является завершение процесса.
Нашей любимой аналогией для каналов является то, как муж и жена вместе моют и сушат тарелки. Один супруг моет тарелки, помещая чистые, но влажные тарелки в сушилку на раковине. Другой супруг вынимает тарелки из сушилки и вытирает их. Моющий тарелки является производителем, сушилка является каналом, а вытирающий является потребителем.[96]
Если вытирающий супруг оказывается быстрее моющего, сушилка становится пустой, и вытирающему приходится ждать, пока не будут готовы новые тарелки. Напротив, если быстрее вытирающий супруг, сушилка наполняется, и моющему приходится ждать, пока она не опустеет, прежде чем помещать в нее тарелки. Это изображено на рис. 9.3.
Рис. 9.3. Синхронизация процессов канала
9.3.2. Очереди FIFO
Для традиционных каналов единственным способом для двух различных программ получить доступ к одному и тому же каналу является наследование дескрипторов файлов. Это означает, что процессы должны быть порожденными от общего родителя или один должен быть предком другого.