2

I'm learning about pipe() system call in Linux. Here is the code snippet I have question about:

#define MSGSIZE 16
char* msg1 = "hello, world #1";
char* msg2 = "hello, world #2";
char* msg3 = "hello, world #3";

int main()
{
    char inbuf[MSGSIZE];
    int p[2], i;

    if (pipe(p) < 0)
        exit(1);

    int pid=fork();
    if(pid==0){
        sleep(2);  ///Making parent process sleep so that child process moves ahead
        write(p[1], msg1, MSGSIZE);
        write(p[1], msg2, MSGSIZE);
        write(p[1], msg3, MSGSIZE);
        printf("Done writing");
    }
    else{
        printf("child process\n");
        for (i = 0; i < 3; i++) {
            read(p[0], inbuf, MSGSIZE);
            printf("%s\n", inbuf);
        }
    }
    return 0;
}

I get output as follows

child process
hello, world #1
hello, world #2
hello, world #3
Done writing

As we see child process has started before write calls where completed. Here it seems that read call is waiting for something.I couldn't find docs in man pages regarding this behaviour. Can someone explain whats happening here in easy terms?

Bodo
  • 9,287
  • 1
  • 13
  • 29
Bruce444
  • 21
  • 3
  • 5
    Yes, it waits for input before returning. – spectras Feb 22 '21 at 09:47
  • Unless the descriptor is non-blocking, `read()` will block until there's input available (Or an error). – Shawn Feb 22 '21 at 09:56
  • Search for the `O_NONBLOCK` flag in the documentation. – Martin Rosenau Feb 22 '21 at 09:58
  • 1
    Or interrupted (check `errno` for `EINTR`). – Erdal Küçük Feb 22 '21 at 10:29
  • BTW: You should check the return value of functions `read` and `write`. It is not guaranteed that it will always write or read a complete message. It may be necessary to repeat the call multiple times to read or write the remaining part. – Bodo Feb 22 '21 at 10:45
  • @Bodo the read and writes are atomic if the sizes are less than a page size. But yeah, if the buffers are filled, the results could be less. – Erdal Küçük Feb 22 '21 at 10:47
  • @ErdalKüçük On Linux, [`read()` from a pipe is atomic up to `PIPE_BUF` bytes](https://www.gnu.org/software/libc/manual/html_node/Pipe-Atomicity.html). – Andrew Henle Feb 22 '21 at 10:49
  • @Andrew Henle Yes, that is true (and the most correct statement). Also the PIPE_BUF size can be modified. But afaik, by default it's the size of a memory page. – Erdal Küçük Feb 22 '21 at 10:53
  • Be careful, you have the parent and child confused with each other. The `fork()` function returns the child's PID in the parent process and 0 in the child process, therefore `if (pid == 0)` --> child, `if (pid != 0)` --> parent. You should also have the child exit and merge back with the parent through a `wait()` once it's done doing its thing. – Nepec Feb 22 '21 at 14:59
  • Also I'm not sure what your program is supposed to do: do you want to have the parent "wake up" each time the child writes a message on the pipe and print this message to stdout? – Nepec Feb 22 '21 at 15:23
  • It is best to check for and handle short reads and writes because code in the future always changes and now its connected to a pty or a network socket or a device file ... etc. Better to just "do it right" the first time. – Zan Lynx Feb 22 '21 at 18:00
  • But it does not know about the incoming message sizes. So all three of your writes could be read as a single incoming message on the read end. So it may have done a single read got all the data and is now waiting for another message. Also if you had written large chunks on one end then the read may only pick up a portion of the message before returning. – Martin York Feb 22 '21 at 20:28

4 Answers4

1

By default, read() blocks until there is input available, or an end of input occurs.

POSIX (IEEE Std 1003-1) standard says that a pipe can hold at least 512 bytes of data. (This means that write() will succeed immediately, even though the data is still in the pipe, and not yet read by the other end.)

Your main process writes the data to the pipe and immediately exits. Because the child process has the read end still open, it can read the data written by the main process later on.

Glärbo
  • 141
  • 2
0

Keyword: Synchronization

The output of your terminal is shared by two processes. How the terminal is handling the synchronization internally, who knows, but you could synchronize your output of your processes (e.g. by shared memory and a mutex or semaphore). So do not rely on the sequence of your output on what was called first and who is waiting for whom.

Or simply use a timestamp for each invocation of printf.

I've answered a similar question here: How Multi-threaded program works in C?.

Erdal Küçük
  • 4,810
  • 1
  • 6
  • 11
0

I took the liberty to polish your code a bit, in particular:

Parent and child processes

I modified your IF statement to correcly identify the parent and child process. From the fork() man page, concerning it's return values:

On success, the PID of the child process is returned in the parent, and 0 is returned in the child. On failure, -1 is returned in the parent, no child process is created, and errno is set appropriately.

In this case it wasn't a big deal but it could definately lead to some problems down the road.

Closing pipes

I closed the pipe ends both when unused and when done reading/writing. See why it's important here: Why should you close a pipe in linux

Exiting and waiting

I made the parent wait for the child's termination in order not to leave an orphaned process which (in more complex and extreme situations) could starve the system for resources.

Here is the code:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define MSGSIZE 16

char* msg1 = "hello, world #1";
char* msg2 = "hello, world #2";
char* msg3 = "hello, world #3";

int main()
{
  char inbuf[MSGSIZE];
  int p[2], i;

  if (pipe(p) < 0)
    exit(1);

  int pid = fork();
  if (pid > 0) {
    // Parent process
    
    close(p[0]);  // Closing unused pipe

    //sleep(2);  
    write(p[1], msg1, MSGSIZE);
    write(p[1], msg2, MSGSIZE);
    write(p[1], msg3, MSGSIZE);
    close(p[1]);  // Closing pipe after all writes are done
    printf("Parent done writing\n");

    wait(NULL);
  } else if (pid == 0) {
    // Child process

    close(p[1]);  // Closing unused pipe
    printf("Child process\n");
    for (i = 0; i < 3; i++) {
      read(p[0], inbuf, MSGSIZE);
      printf("%s\n", inbuf);
    }
    close(p[0]);

    exit(0);  // Child has terminated succesfully, exit.

  } else {
    fprintf(stderr, "Fork error.\n");
    return 1;
  }

  return 0;
}
Nepec
  • 67
  • 7
0

The simple answer is that read waits until some input is available.

More precisely, files can be opened in blocking or non-blocking mode. Blocking mode is the default. You can choose non-blocking mode by passing O_NONBLOCK in the flags argument to open (e.g. open(filename, O_RDONLY | O_NONBLOCK)) or by calling fcntl(fd, F_SETFL, O_NONBLOCK) after opening the file.

In blocking mode, read normally waits until it has read at least one byte. Note that it does not wait until it has read as many bytes as requested, even if that many bytes are available. How many bytes are returned in a single read call can depend on the whims of the process scheduler. “Normally” means that you can write a loop like

do n = read(…);
while (n > 0 || (n < 0 && errno == EINTR));

and be confident that it isn't going to be a tight loop. Note the case errno == EINTR, which indicates that read was interrupted before read anything. (This isn't conveyed by returning 0, which would be more logical, because returning 0 indicates the end of the file.)

If the file is in nonblocking mode, another case where nothing has been read is errno == EAGAIN. In non-blocking mode, if no data is available, read returns immediately, so a loop similar to the one above would be bad.

All of this applies to write as well, except for 0 meaning end-of-file.

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254