Lectures‎ > ‎

Week05


I/O Redirection

  • How does the shell do
    • date > out
  • The quick answer:
    • We modify the file descriptor table
  • We need to take a closer look at how the file descriptor table works


File Descriptor Table (FD Table)

  • Each Process has a file descriptor table in the kernel (in the Process Control Block)
  • fork() duplicates the file descriptor table
  • Draw picture of two file descriptor tables with stdin, stdout, stderr.


Modifying the FD Table

  • In order to redirect input/output, we need to modify the file descriptor table
  • New system call:
    • int dup(int oldfd)
    • Makes a copy of the given fd in the first available fd table slot
  • We can use close() to open up slots


Redirection Example

redirect.c

/* redirect.c - example of redirection */

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

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

        if ((fd = open("out", O_CREAT | O_WRONLY, 0644)) < 0) {
                printf("cannot open out\n");
                exit(1);
        }
        
        id = fork();

        if (id == 0) {
                /* we are in the child */
                /* close stdout in child */
                close(1);
                dup(fd);   /* replace stdout in child with "out" */
                close(fd);
                execl("/bin/date", "date", NULL);
        }

        /* we are in the parent */
        close(fd);
        id = wait(NULL);

        return 0;
}



Interprocess Communication (IPC)

  • It is often useful and necessary to allow different processes to communicate with each other
  • There are two basic types of IPC
    • Explicit mechanisms: System call interface such as pipes, sockets, message passing, and remote procedure call (RPC)
    • Shared memory: Using virtual memory, allow processes to share a portion of memory.  Processes communicate by accessing shared variables.
  • Today we look at UNIX pipes; later we will look at shared memory, message passing, and RPC.


UNIX Pipes

  • A UNIX pipe is an IPC mechanism
  • A pipe allows for the exchange of data between processes
  • A pipe is used to send a stream of character data
  • Basic idea
    • Create a pipe using the pipe() system call
    • At the user level, a pipe is just a pair of file descriptors
      • One for reading (0)
      • One for writing (1)
    • Use read() and write() system calls to receive and send data
  • Order: FIFO (First in, first out)


Uses for Pipes

  • General mechanism to allow related processes running on the same machine to communicate
    • Note: we need something else to allow inter-machine communication (i.e., sockets)
  • Use by the shell to "connect" programs
    • ls | wc
      • Gives the number of files in current directory
    • who | wc
      • Gives the number of users logged in
    • who | sort | lpr  # prints a sorted list of users
  • Note that shell pipes are unidirectional
  • System pipes can be bidirectional


The Pipe System Call

  • int pipe(int filedes[2])
    • Check return value for possible errors
    • int filedes[2] is just an array of two ints
    • filedes[0] is for reading (the "read end")
    • filedes[1] is for writing (the "write end")
    • Remember the read end is 0 (like stdin) and the write end is 1 (like stdout)
    • Usage

      int filedes[2];
      pipe(filedes);
  • Draw picture of a pipe with "hello world"




Connecting Processes

  • Consider a parent that wants to send data to a child
  • In Parent:
    • create a pipe
      • pipe(filedes)
    • fork() a child
    • close(filedes[0]) (don't leave open ends)
    • write to child with
      • write(filedes[1], ...)
    • close(filedes[1]) on completion (important)
  • In Child:
    • close(filedes[1]) (don't leave open ends)
    • optional: redirect stdin and use execl()
    • read from parent with
      • read(filedes[0], ...)
    • close(filedes[0]) when done


Notes on Pipes

  • Can setup many configurations
    • Parent writes to a child (1 pipe)
    • Child writes to a parent (1 pipe)
    • Parent writes to a child and child writes to parent (2 pipes)
    • Parent connects to child (1 pipe, like the shell)
    • Parent connects two children (1 pipe)
  • One processes with a common parent can communicate with a standard pipe
    • There is a "named" pipe that can exist in the filesystem
  • You need to close the ends of the pipe to ensure correct termination
  • Use dup() to redirect stdin or stdout to a pipe


Pipe Examples

pipe.c

/* pipe.c - example of a pipe */

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

int main(int argc, char **argv)
{
        pid_t id;
        int count;
        char buf[100];
        int fildes[2];

        pipe(fildes);

        id = fork();

        if (id == 0) {
                //sleep(1);
                /* we are in the child */
                /* close "write" end of pipe */
                close(fildes[1]);
                if ((count = read(fildes[0], buf, 100)) < 0) {
                        fprintf(stderr, "cannot read from pipe\n");
                        exit(1);
                }
                printf("buf[] = %s\n", buf);
                close(fildes[0]);
                exit(0);
        } else {
                /* we are in the parent */
                /* close "read" end of pipe */
                close(fildes[0]);
                if (write(fildes[1], "Hello child!", 13) < 0) {
                        fprintf(stderr, "cannot write to pipe\n");
                }
                close(fildes[1]);
                id = wait(NULL);
        }

        return 0;
}


pipe-exec.c

/* pipe-exec.c - use a pipe to send "input" to another program */

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

int main(int argc, char **argv)
{
        pid_t id;
        int count;
        int fildes[2];

        pipe(fildes);

        if ((id = fork()) == 0) {
                /* we are in the child */
                close(0);         /* close stdin */
                dup(fildes[0]);   /* put read end of pipe into stdin index */
                close(fildes[1]); /* close "write" end of pipe */

                if (execlp("sort", "sort", NULL) < 0) {
                        printf("execl failed\n");
                        exit(1);
                }
        } else {
                /* we are in the parent */
                /* close "read" end of pipe */
                close(fildes[0]);
                /* send some data to sort */
                write(fildes[1], "bbb\n", 4);
                write(fildes[1], "ttt\n", 4);
                write(fildes[1], "aaa\n", 4);
                /* close write end to tell sort we are done */
                close(fildes[1]);
                id = wait(NULL); /* wait for child */
        }

        return(0);
}

Working with Directories

  • opendir(), readdir(),closedir()
  • man readdir (on Linux)
  • man 5 dir (on Mac)
  • struct dirent

readdir_example.c

#include<dirent.h>
#include<stdbool.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int
main(int argc, char **argv)
{
    DIR *dirp;
    struct dirent *dp;
    char *name = argv[1];
    int len;
    bool found = false;

    /* Open the current directory */
    dirp = opendir(".");
    if (dirp == NULL) {
        printf("Cannot opendir()\n");
        exit(-1);
    }
            
    len = strlen(name);
    while ((dp = readdir(dirp)) != NULL) {
        if (strlen(dp->d_name) == len && strcmp(dp->d_name, name) == 0) {
            found = true;
       }
    }
    closedir(dirp);

    if (found) {
        printf("Found %s in current directory.\n", name);
    } else {
        printf("Cannot find %s in current directory.\n", name);
    }

    return 0;
}
Comments