Родительский и дочерний процессы
Следующий логический шаг в нашем изучении вызова pipe — разрешить дочернему процессу быть другой программой, отличной от своего родителя, а не просто другим процессом, выполняющим ту же самую программу. Сделать это можно с помощью вызова exec. Единственная сложность заключается в том, что новому процессу, созданному exec, нужно знать, какой файловый дескриптор применять для доступа. В предыдущем примере этой проблемы не возникло, потому что дочерний процесс обращался к своей копии данных file_pipes. После вызова exec возникает другая ситуация, поскольку старый процесс заменен новым дочерним процессом. Эту проблему можно обойти, если передать файловый дескриптор (который, в конце концов, просто число) как параметр программе, вновь созданной с помощью вызова exec.
Для того чтобы посмотреть, как это работает, вам понадобятся две программы (упражнение 13.7). Первая —
exec1. Для получения первой программы исправьте pipe2.c, превратив ее в pipe3.c. Измененные строки затенены.
#include
#include
#include
#include
int main() {
int data_processed;
int file_pipes[2];
const char somedata[] = "123";
char buffer[BUFSIZ + 1];
pid_t fork_result;
memset(buffer, '\0', sizeof(buffer));
if (pipe(file_pipes) == 0) {
fork_result = fork();
if (fork_result == (pid_t)-1) {
fprintf(stderr, "Fork failure");
exit(EXIT_FAILURE);
}
if (fork_result == 0) {
} else {
data_processed = write(file_pipes[1], some_data, strlen(some_data));
}
}
exit(EXIT_SUCCESS);
}
2. Программа-потребитель pipe4.c, читающая данные, гораздо проще:
#include
#include
#include
#include
int main(int argc, char *argv[]) {
int data_processed;
char buffer[BUFSIZ + 1];
int file_descriptor;
memset(buffer, '\0', sizeof(buffer));
sscanf(argv[1], "%d", &file_descriptor);
data_processed = read(file_descriptor, buffer, BUFSIZ);
printf("%d — read %d bytes: %s\n", getpid(), data_processed,
buffer);
exit(EXIT_SUCCESS);
}
Выполнив pipe3 и помня о том, что она вызывает программу pipe4, вы получите вывод, аналогичный приведенному далее:
$ ./pipe3
22460 - wrote 3 bytes
22461 - read 3 bytes: 123
Как это работает
Программа pipe3 начинается как предыдущий пример, используя вызов pipe для создания канала и затем вызов fork для создания нового процесса. Далее она применяет функцию sprintf для сохранения в буфере номера файлового дескриптора чтения из канала, который формирует аргумент программы pipe4.
Вызов execl применен для вызова программы pipe4. В нем использованы следующие аргументы:
□ вызванная программа;
□ argv[0], принимающий имя программы;
□ argv[1], содержащий номер файлового дескриптора, из которого программа должна читать;
□ (char *)0, завершающий список параметров.
Программа pipe4 извлекает номер файлового дескриптора из строки аргументов и затем читает из него данные.
Чтение закрытых каналов
Прежде чем двигаться дальше, необходимо более внимательно рассмотреть файловые дескрипторы, которые открыты. До этого момента вы разрешали читающему процессу просто читать какие-то данные и завершаться, полагая, что ОС Linux уберет файлы в ходе завершения процесса.
В большинстве программ, читающих данные из стандартного ввода, это делается несколько иначе, чем в виденных вами до сих пор примерах. Обычно программы не знают, сколько данных они должны считать, поэтому они, как правило, выполняют цикл — чтение данных, их обработка и затем снова чтение данных и так до тех пор, пока не останется данных для чтения.