Понимаю ли я, как работают файловые дескрипторы Unix в C?
короткая программа ниже предназначена для перебора argv, переданного из командной строки, и выполнения каждого аргумента. Это не моя домашняя работа, а скорее то, что я делаю, готовясь к выполнению домашней работы.
первый аргумент получает входные данные от STDIN и STDOUT и записывается в канал. В конце каждой итерации (кроме последней) файловые дескрипторы меняются местами, так что канал, записанный последним исполнителем, будет считан следующим. Таким образом, я намереваюсь, например, для
./a.out /bin/pwd /usr/bin/wc
чтобы распечатать только длину рабочего каталога. Код
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
main(int argc, char * argv[]) {
int i;
int left[2], right[2], nbytes; /* arrays for file descriptors */
/* pointers for swapping */
int (* temp);
int (* leftPipe) = left;
int (* rightPipe) = right;
pid_t childpid;
char readbuffer[80];
/* for the first iteration, leftPipe is STDIN */
leftPipe[0] = STDIN_FILENO;
leftPipe[1] = STDOUT_FILENO;
for (i = 1; i < argc; i++) {
/* reopen the right pipe (is this necessary?) */
pipe(rightPipe);
fprintf(stderr, "%d: %sn", i, argv[i]);
fprintf(stderr, "%d %d %d %dn", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);
if ((childpid = fork()) == -1) {
perror("fork");
exit(1);
}
if (childpid == 0) {
/* read input from the left */
close(leftPipe[1]); /* close output */
dup2(leftPipe[0], STDIN_FILENO);
close(leftPipe[0]); /* is this necessary? A tutorial seemed to be doing this */
/* write output to the right */
close(rightPipe[0]); /* close input */
dup2(rightPipe[1], STDOUT_FILENO);
close(rightPipe[1]);
execl(argv[i], argv[i], NULL);
exit(0);
}
wait();
/* on all but the last iteration, swap the pipes */
if (i + 1 < argc) {
/* swap the pipes */
fprintf(stderr, "%d %d %d %dn", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);
temp = leftPipe;
leftPipe = rightPipe;
rightPipe = temp;
fprintf(stderr, "%d %d %d %dn", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);
}
}
/* read what was last written to the right pipe */
close(rightPipe[1]); /* the receiving process closes 1 */
nbytes = read(rightPipe[0], readbuffer, sizeof(readbuffer));
readbuffer[nbytes] = 0;
fprintf(stderr, "Received string: %sn", readbuffer);
return 0;
}
обновление: во всех приведенных ниже тестовых случаях я первоначально использовал /bin / wc, но который wc reveiled, что ватерклозет совсем не там, где я думал. Я в процессе внесения изменений в результаты.
вывод в тривиальном случае (.a.out / bin / pwd), как и ожидалось:
1: /bin/pwd
Received string: /home/zeigfreid/Works/programmatical/Langara/spring_2012/OS/labs/lab02/play
результат выполнения этой программы с первый пример (.a.out / bin / pwd / usr/bin / wc):
1: /bin/pwd
0 1 3 4
3 4 0 1
2: /bin/wc
в этот момент терминал зависает (возможно, ожидая ввода).
как вы можете видеть, строка не принимается. Я предполагаю, что я сделал что-то неправильно выше, либо при замене указателей, либо что я не понимаю файловые дескрипторы unix. Моя задача, в конце концов, будет заключаться в интерпретации произвольно длинных труб, и это одна из идей, которые я имел для решения проблемы. У меня проблемы. судить, на правильном ли я пути, чтобы залаять на дерево. Понимаю ли я файловые дескрипторы unix?
обновление:
запустив его с /bin/ls в качестве второго аргумента, я получил следующий результат (числа являются файловыми дескрипторами в разных точках):
1: /bin/pwd
0 1 3 4
0 1 3 4
3 4 0 1
2: /bin/ls
3 4 5 6
Received string: a.out
log
pipe2.c
play.c
@
там все еще есть какой-то мусор в конце, но я теперь больше обеспокоен тем, что я не понимаю указателей! Эти две команды независимы друг от друга, хотя, они не использовать трубы.
обновление: символ мусора был от не закрытия строки. Теперь я его закрываю, и никакого мусора.
2 ответов
подвешивание вызвано тем, что конец записи "правой" трубы неправильно закрыт в основном процессе после разветвления. Из-за этого, wc
никогда не перестанет читать (в конце концов, основной процесс все еще может писать материал в трубу!). Он прекращает чтение только после закрытия всех копий файлового дескриптора конца записи.
вот исправленная версия:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char * argv[])
{
int i;
int left[2], right[2], nbytes; /* arrays for file descriptors */
/* pointers for swapping */
int (* temp);
int (* leftPipe) = left;
int (* rightPipe) = right;
pid_t childpid;
char readbuffer[80];
leftPipe[0] = STDIN_FILENO;
// no need to assign leftPipe[1] here, it will not be used
for (i = 1; i < argc; i++) {
pipe(rightPipe); // create new pipe
fprintf(stderr, "%d: %s\n", i, argv[i]);
fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);
if ((childpid = fork()) == -1) {
perror("fork");
exit(1);
}
if (childpid == 0) {
// use the reading end of the left pipe as STDIN
dup2(leftPipe[0], STDIN_FILENO);
// use the writing end of the right pipe as STDOUT
dup2(rightPipe[1], STDOUT_FILENO);
// close reading end of the right pipe
close(rightPipe[0]);
execl(argv[i], argv[i], NULL);
exit(0);
}
// IMPORTANT!! close writing end of the right pipe, otherwise
// the program will hang (this is the main bug in your original
// implementation)
close(rightPipe[1]);
// wait properly!
waitpid(childpid, NULL, 0);
/* on all but the last iteration, swap */
if (i + 1 < argc) {
fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);
temp = leftPipe;
leftPipe = rightPipe;
rightPipe = temp;
fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);
}
}
nbytes = read(rightPipe[0], readbuffer, sizeof(readbuffer));
readbuffer[nbytes] = 0;
fprintf(stderr, "Received string: %s\n", readbuffer);
return 0;
}
выход:
>> ./a.out /bin/ls /bin/cat /usr/bin/wc
1: /bin/ls
0 32767 3 4
0 32767 3 4
3 4 0 32767
2: /bin/cat
3 4 4 5
3 4 4 5
4 5 3 4
3: /usr/bin/wc
4 5 5 6
Received string: 266 294 4280
если у вас конкретные вопросы об этом решении, пожалуйста, дайте мне знать :) есть также некоторые другие незначительные проблемы с вашим исходным кодом:
- использование указателей не нужно, мы можем просто скопировать вокруг труб (производительность, безусловно, не будет проблемой ;)
-
int
вместоsize_t
- вы не исправили все предупреждения, которые будут представлены вам при компиляции с помощью
-Wall
флаг
Если вам интересно, вот как Я бы написал:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
size_t i, nbytes;
int left[2], right[2], tmp[2];
pid_t childpid;
char readbuffer[80];
left[0] = STDIN_FILENO;
for (i = 1; i < argc; ++i) {
pipe(right);
switch ((childpid = fork())) {
case -1:
perror("fork");
exit(1);
case 0:
dup2(left[0], STDIN_FILENO);
dup2(right[1], STDOUT_FILENO);
close(right[0]);
execl(argv[i], argv[i], NULL);
default:
close(right[1]);
waitpid(childpid, NULL, 0);
}
if (i == argc - 1) break;
memcpy(tmp, left, sizeof tmp);
memcpy(left, right, sizeof left);
memcpy(right, tmp, sizeof right);
}
nbytes = read(right[0], readbuffer, sizeof readbuffer);
readbuffer[nbytes] = 0;
fprintf(stderr, "Received string: %s\n", readbuffer);
return 0;
}
чтобы исправить мусор в конце вывода, добавьте следующую строку перед final printf
.
readbuffer[nbytes] = 0;
Что касается проблемы подвешивания - мне нужно немного больше подумать, чтобы исправить это. Я предполагаю, что это связано с водопроводом и буферизацией.