UP | HOME

Process I/O
Systems Programming
COP-3402

Table of Contents

Process review

What's a process?

How do we create new processes in UNIX?

Interprocess Communication

  • Processes are isolated
  • After exec, different programs
  • Fork/exec
    • No longer same address space
    • No longer same running program
    • Process isolation protects from accessing each other's memory
    • How do we pass information between them?

Example

Pass ls to wc

What do ls and wc do?

Using bash

ls | wc

Send output of ls to input of wc

  • ls and wc and separate processes
  • How does using | send information between them?

What is standard I/O in UNIX?

Pipe overview

  • Pipe for inter-process communication
  • Create pipe
    • Two new files
    • Write to one, data can be read from the other

Diagram

  • Pipe
  • ls sends info to pipe
  • wc reads info from pipe
  • Network sockets
    • Similar file-like interface (read/write vs. send/recv)
    • Different implementation
    • Swap out pipe with socket

Creating pipes

Symbol Reference Reading
pipe() man 2 pipe LPI 44.2

man 2 pipe

pipe.c

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

int main(int argc, char **argv) {
  int pipefd[2];
  char buf;
  pid_t cpid;

  if (pipe(pipefd) == -1) {
    perror("pipe");
    exit(EXIT_FAILURE);
  }

  cpid = fork();
  if (-1 == cpid) {
    perror("fork");
    exit(EXIT_FAILURE);
  }

  if (0 == cpid) {
    // child
    close(pipefd[1]);  // close write end (parent does writing)

    while (read(pipefd[0], &buf, 1) > 0) {
      write(STDOUT_FILENO, &buf, 1);
    }

    write(STDOUT_FILENO, "\n", 1);
    close(pipefd[0]);
    _exit(EXIT_SUCCESS);
  } else {
    // parent
    close(pipefd[0]);  // close read end (child does reading)
    write(pipefd[1], argv[1], strlen(argv[1]));
    close(pipefd[1]);
    wait(NULL);
    exit(EXIT_SUCCESS);
  }
}

Piping between different programs

Example: "ls | wc"

Setup

  • Three processes
    • Master process
    • ls
    • wc
  • Master process
    • Creates the pipe
    • Fork/exec the two programs
    • Replace stdio with the pipe

Algorithm for master process

  • Create pipe
  • Fork process for ls
    • Replace ls's stdout with the pipe
    • Run ls with the new stdout
  • Fork process for wc
    • Replace wc's stdin with the pipe
    • Run wc with the new stdin
  • Wait for processes the finish

Questions:

  • Why does forking itself not run ls or wc? What does forking do?
  • Why is ls's stdout replaced, not its stdin?
  • What happens to ls's stdin?

Diagram

  • Master process: filter.c
  • Create pipe
  • Fork new process
    • Redirect process's output to the pipe
    • Run ls
  • Fork another process
    • Redirect process's input to the pipe
    • Run wc
  • Let everything run as usual
  • Note the benefit of having stdio
    • Programs don't need to written specifically for piping. Availability of stdio makes this possible.

filter.c

  • A program that runs "ls | wc"
    • Create a pipe
    • Fork-exec two processes
    • Make ls write to the pipe
    • Make wc read from the pipe

Implementat in the style of stepwise refinement.

Pseudo-code

pipefd = new pipe  // create a new pipe
leftpid = fork     // fork process for ls
if in child        // code for the child
  close(pipefd[read_end]) // close read end, since left child writes
  dup2(pipefd(write_end), stdout)  // replace stdou with the pipe
  exec("ls")
  // ls is running, control not returned here

// back in the paren
rightpid = fork
if in child
  close(pipefd[write_end]) // close the write end, since right child reads
  dup2(pipefd(read_end), stdin) // replace stdin with the pipe's output
  exec("wc")
  // wc is running, control not returned here

// back in the parent
close(pipefd[read_end])
close(pipefd[write_end])
wait()  // wait for one child
wait()  // wait for the other

Documentation

Symbol Reference Reading
pipe() man 2 pipe LPI 44.2
dup2() man 2 close LPI 44.4

pipe_exec.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>    // pipe(), dup2()

// man 2 pipe
// man 2 dup2

int main(int argc, char **argv) {
  int pipefd[2];
  char buf;
  pid_t leftpid, rightpid;

  if (pipe(pipefd) == -1) {
    perror("pipe");
    exit(EXIT_FAILURE);
  }

  // pipefd[1] is the write end
  // pipefd[0] is the read end

  // ls | wc

  // create a process for the first program, ls
  leftpid = fork();
  if (-1 == leftpid) {
    perror("fork");
    exit(EXIT_FAILURE);
  }

  if (0 == leftpid) {
    // child
    if (close(pipefd[0]) == -1) {  // close read end, since that will be used by the second program
      perror("close");
      exit(EXIT_FAILURE);
    }

    // redirect the program's standard out with the input to the pipe (write end, which is pipefd[1])
    printf("rightpipe dup2(%d, %d)\n", pipefd[1], STDOUT_FILENO);
    if (dup2(pipefd[1], STDOUT_FILENO) == -1) {
      perror("dup2");
      exit(EXIT_FAILURE);
    }

    if (close(pipefd[1]) == -1) {
      perror("close");
      exit(EXIT_FAILURE);
    }

    char *prog = "/usr/bin/ls";
    char *newargv[] = { NULL };
    char *newenv[] = { NULL };
    execve(prog, newargv, newenv);
    perror("execve");
    _exit(EXIT_FAILURE);
  } else {
    // in parent, continue to fork second program
  }

  rightpid = fork();
  if (-1 == rightpid) {
    perror("fork");
    exit(EXIT_FAILURE);
  }

  if (0 == rightpid) {
    // child

    if (close(pipefd[1]) == -1) { // close write, since that is being used by the first program
      perror("close");
      exit(EXIT_FAILURE);
    }

    // redirect the program's standard in with the output of the pipe (read end, which is pipefd[0])
    printf("leftpipe dup2(%d, %d)\n", pipefd[0], STDIN_FILENO);
    if (dup2(pipefd[0], STDIN_FILENO) == -1) {
      perror("dup2");
      exit(EXIT_FAILURE);
    }

    if (close(pipefd[0]) == -1) {
      perror("close");
      exit(EXIT_FAILURE);
    }

    char *prog = "/usr/bin/wc";
    char *newargv[] = { NULL };
    char *newenv[] = { NULL };
    execve(prog, newargv, newenv);
    perror("execve");
    _exit(EXIT_FAILURE);
  } else {
    // in parent, continue to wrap up
  }

  // back in parent
  // close pipefds
  if (close(pipefd[0]) == -1) {
    perror("close");
    exit(EXIT_FAILURE);
  }
  if (close(pipefd[1]) == -1) {
    perror("close");
    exit(EXIT_FAILURE);
  }
  // wait for both children
  if (wait(NULL) == -1) {
    perror("wait");
    exit(EXIT_FAILURE);
  }
  if (wait(NULL) == -1) {
    perror("wait");
    exit(EXIT_FAILURE);
  }
}

wait.c

#include <stdio.h>
#include <unistd.h>    // fork()
#include <stdlib.h>    // exit()
#include <inttypes.h>  // intmax_t
#include <sys/wait.h>  // wait()

// man 2 fork
// man 2 execve
// man 2 wait

int main(int argc, char **argv) {
  pid_t pid;

  switch (pid = fork()) {
  case -1:
    perror("fork");
    exit(EXIT_FAILURE);
    break;
  case 0:
    // child
    puts("inside child process\n");
    char *prog = "/usr/bin/ls";
    char *newargv[] = { NULL, "./", NULL };
    char *newenv[] = { NULL };
    execve(prog, newargv, newenv);
    perror("execve");
    _exit(EXIT_FAILURE);
    break;
  default:
    // parent
    int wstatus;
    printf("child pid: %jd\n", (intmax_t) pid);
    do {
      pid_t wpid = wait(&wstatus);
      if (-1 == wpid) {
        perror("waid");
        exit(EXIT_FAILURE);
      }

      if (WIFEXITED(wstatus)) {
        printf("exited, status=%d\n", WEXITSTATUS(wstatus));
      } else if (WIFSIGNALED(wstatus)) {
        printf("killed by signal %d\n", WTERMSIG(wstatus));
      } else if (WIFSTOPPED(wstatus)) {
        printf("stopped by signal %d\n", WSTOPSIG(wstatus));
      } else if (WIFCONTINUED(wstatus)) {
        printf("continued\n");
      }
    } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
    exit(EXIT_SUCCESS);
    break;
  }
}

Author: Paul Gazzillo

Created: 2024-09-30 Mon 14:15

Validate