I understand how Unix file descriptors work in C?

The brief program below is designed to iterate over argv passed from the command line and execute each argument. This is not my homework, but rather what I do, preparing for my homework.

The first argument is entered from STDIN and STDOUT and written to the channel. At the end of each iteration (except the last), the file descriptors are swapped so that the channel recorded by the last exec will be read next. Thus, I suppose, for example, for

./a.out /bin/pwd /usr/bin/wc 

to print only the length of the working directory. The code follows

 #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: %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) { /* 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 %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]); } } /* 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: %s\n", readbuffer); return 0; } 

UPDATE : in all of the test cases below, which I originally used / bin / wc, but which wc showed that the water cabinet was not at all where I was thinking. I am in the process of amending the results.

The output in the trivial case (./a.out/bin/pwd) looks as expected:

 1: /bin/pwd Received string: /home/zeigfreid/Works/programmatical/Langara/spring_2012/OS/labs/lab02/play 

The result of starting this program with the first example (./a.out/bin/pwd/usr/bin/wc):

 1: /bin/pwd 0 1 3 4 3 4 0 1 2: /bin/wc 

At this point, the terminal freezes (maybe waiting at the entrance).

As you can see, the line is not accepted. I suppose that I did something wrong above, either when replacing the pointers, or that I do not understand the unix file descriptors. Ultimately, my task will be to interpret arbitrarily long pipes, and this is one of the ideas that I had to solve the problem. I have problems evaluating whether I am on the right track barking in a tree. Do I understand unix file descriptors?

UPDATE:

Running it with / bin / ls as the second argument, I got the following result (numbers are file descriptors at different points):

 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 @ 

There is still trash in the end, but now it bothers me more that I don't understand the pointers! The two teams are independent of each other, although in reality they do not use the pipe.

UPDATE : garbage symbol was not closed. Now I close it and not trash.

+7
source share
2 answers

Suspension is caused by the fact that the end of the recording of the β€œcorrect” pipe is not properly closed in the main process after forking. Because of this, wc will never stop reading (in the end, the main process can still write material to the pipe!). It stops reading only after closing all copies of the file descriptor of the end of the record.

Here is the fixed version:

 #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; } 

Output:

  >> ./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 

If you have specific questions about this solution, let me know :) There are also some other minor issues with your source code:

  • using pointers is not necessary, we can just copy around the pipes (performance will certainly not be a problem;)
  • int used instead of size_t
  • you did not clear all warnings that will be presented to you when compiling with the -Wall flag

If you're interested, here is how I would write this:

 #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; } 
+2
source

To fix garbage at the end of the output, add the following line before the final printf .

 readbuffer[nbytes] = 0; 

As for the suspension problem, I need to think a bit to fix it. I assume this is due to plumbing and buffering.

0
source

All Articles