Inter-Process Communication in Unix

Pipes

  1. Include Files:
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/time.h>
    #include <sys/mman.h>
    #include <sys/un.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <socket.h>
  2. int pipe(int filedes[2]);
    Opens two file descriptors: filedes[0] will be open for reading, and filedes[1] will be open for writing. Filedes[0] will read what is written into filedes[1]. This is a mechanism for inter-process communication between a parent process to its child:
    int n, fd[2];
    pid_t pid;
    char line[1024];
    int main(void)
    {
    if (pipe(fd) < 0)
    { perror(“pipe”); exit(1); }
    if ((pid = fork()) < 0)
    { perror(“fork”); exit(1); }
    else if (pid > 0) {
    close(fd[0]);
    write(fd[1], “hello world\n”, 12);
    while(wait(null) < 0);
    } else {
    close(fd[1]);
    n = read(fd[0], line, 1024);
    write(STDOUT_FILENO, line, n);
    }
    exit(0);
    }

    In a production code read() and write() should also be checked for errors.
  3. Fifos

  4. int mkfifo(const char *pathname, mode_t mode);
    Creates a file of type fifo, or named pipe. After the file is created, it can be accessed using the usual open(), read(), write(), close(), unlink() and so forth.
    Opening a fifo for reading will block until someone else has opened the fifo for writing, unless the fifo is opened with the O_NONBLOCK option (in the second argument of the open() system call).
  5. Fifos are similar to pipes but can be used between processes without a parent-child relationship. Here is an example of using fifos from the shell. Both prog2 and prog3 need the output from prog1, and we don’t want to use a temporary file. So we write:
    mkfifo fifo1
    prog3 < fifo1 &
    prog1 < input_file | tee fifo1 | prog2
  6.  

    Select

  7. int select(int maxfdp1, fd_set *readfds, fd_set *writefds,
    fd_set *execptfds, struct timeval *tvptr);

    Waits for an I/O event to occur in one of many file descriptors. You can wait until they are ready for reading, ready for writing, or an exception occurred at them. The first argument is the maximum number of file descriptors that are in any of the sets plus 1. The tvptr argument specifies the maximum time to wait (can be 0.0 seconds as well), or can be NULL. If it’s NULL, we wait until one of the descriptors is ready or until we get a signal.
    The function returns –1 on error, or the number of ready file descriptors (can be 0 if we timed out). If the process receives a signal while in select(), the function returns with –1 and sets errno to EINTR. Therefore, many times signals are blocked during select().
  8. struct timeval {
    long tv_sec; /* seconds */
    long_tv_usec; /* microseconds */
    }
    This is the time structure used by select() and many other routines that handle time. Most system clocks are accurate to the level of 10-50 microseconds.
  9. FD_ZERO(fd_set *fdset);
    FD_SET(int fd, fd_set *fdset);
    FD_CLR(int fd, fd_set *fdset);
    FD_ISSET(int fd, fd_set *fdset);
    These functions manupulate the “file descriptor set” data type used for three of the five arguments of the select system call. They allow initializing, adding to, removing from and checking membership of a set.
  10. Memory Mapped Files

  11. caddr_t mmap(caddr_t addr, size_t len, int prot, int flag,
    int filedes, off_t off);
    Maps a file into memory. ‘filedes’ is the descriptor of the file to map; the file must be open, however closing it later will not un-map it. ‘addr’ is the address in memory to map to – it is recommended to pass 0 as this argument to let the system decide. ‘off’ is the offset in the file from which to map; ‘len’ is the number of bytes to map, starting from that offset. ‘prot’ is the protection mode for the mapped file – it must match the permissions the file was opened with and can be a combination of PROT_READ, PROT_WRITE and PROT_EXEC.
    The function returns the address in which the mapped file is (caddr_t is usually defined as char*). If ‘flag’ is MAP_SHARED then store operations will modify the mapped file: In other words, writing to this memory will be equivalent to the write() system call. If ‘flag’ is MAP_PRIVATE, store operations will work on a copy of the file and will not really change it. The function returns (caddr_t)(-1) on error.
  12. int munmap(caddr_t addr, size_t len);
    Unmaps a file. This also happens automatically when the process terminates.
  13. Copying a file using mmap() is much faster than using read() and write(), because there is no need to copy buffers from kernel to user space and back during the copy. In order to do the copying, follow these steps:
    * Open the source file with open(), then map it with mmap().
    * Create the destination file with open().
    * Read the size of the input file using fstat(), then set the size of the destination file to
    that same size using lseek(fdout, src_size-1, SEEK_SET) and write(fdout, “”, 1).
    * Map the destination file with mmap(), with the flag argument MAP_SHARED.
    * Call memcpy(dst, src, src_size) to copy the files by memory-to-memory copy.
    * Either unmap the files or exit the program.
  14. Memory mapped files can also be used to declare shared memory between processes. The special file “/dev/zero” is an infinite stream of zeros when read, and ignores anything written to it. Therefore if a parent process memory-maps this file with MAP_SHARED and initializes this memory, and then forks, the child processes can use the same pointers to write to this shared memory region. The size of the shared memory region to create is the second argument of mmap(), and munmap() can be used to free such memory.
  15. Sockets

  16. Sockets allow client/server computing over a large network such as the Internet. There are two types of them: Server sockets, which listen passively for requests, and client sockets, which initiate the contact with servers. Once a connection is made, both the client and the server receive file descriptors and can use the usual read() and write() system calls. The connection is full-duplex.
    We will only use the TCP/IP protocol here, since it is the most popular. Servers call socket(), bind() and listen() before they can accept() requests; clients just call socket() and then they can already connect() to a server.
  17. int socket(int domain, int type, int protocol);
    Creates an endpoint for communication and returns a descriptor, or –1 on error. The returned descriptor is a “socket descriptor”, not a file descriptor. The domain argument is the family of protocols the socket will use: AF_UNIX (internal), AF_INET (Internet), and others. The socket has an associated type as well: SOCK_STREAM (connection based, fifo and reliable such as TCP), SOCK_DGRAM (datagram based, unordered, unreliable such as UDP), SOCK_RAW (simple bytes), SOCK_SEQPACKET and SOCK_DRM. We will use the TCP protocol, so: socket(PF_INET, SOCK_STREAM, 0).
  18. int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
    Gives the socket, sockfd, the local address my_addr. my_addr is addrlen bytes long. Traditionally, this is called "assigning a name to a socket". The exact contents of struct sockaddr defer between protocols; we use TCP/IP and therefore will use this address:
    /* Socket address, internet style. */
    struct sockaddr_in { short sin_family;
    u_short sin_port; struct in_addr sin_addr; char sin_zero[8];}
    Where sin_family is AF_INET, sin_port and in_addr contain the IP address and port of the computer to bind to, and sin_zero should just be eight (ignored) zeros. The in_addr field holds an IP address; servers should use sockaddr.in_addr.s_addr = INADDR_ANY.
  19. int listen(int s, int backlog);
    For a connection based protocol (when the socket type is either SOCK_STREAM or SOCK_SEQPACKET), this function declares that the socket is ready to receive requests, and also specifies the length of the queue of waiting requests to allocate. The backlog parameter is limited to 5 (this is a historical bug).
  20.  

  21. int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
    Connects a socket descriptto a remote address. In a client/server program, clients connect using the server’s address in order to start a communication. After connect, the client can simply use sockfd as a file descriptor in calls to read, write, send and recv.
  22. int accept(int sfd, struct sockaddr *addr, int *addrlen);
    Waits until there’s at least one request on the queue for the given socket. Removes a connection request from the queue, and returns a file descriptor for that request. That file descriptor can be used with read, write, send and recv to communicate with the client. The function returns –1 on error; a common error is being interrupted by a signal, in which errno will be set to EINTR.
  23. int send(int s, const void *msg, int len, unsigned int flags);
    int recv(int s, void *buf, int len, unsigned int flags);
    Send and receive data over a socket; read and write can be used as well, but these functions have extra power. Both functions can have a flag of MSG_OOB to process out-of-band data, and recv can have a flag of MSG_PEEK to peek at incoming messages without removing them from the read queue. Flags can be combined using ‘or’.
    The first three arguments are the same as in read() and write(), except that only sockets can be used. The functions return the number of bytes read/written or –1 on error.
  24. int getsockname(int s, struct sockaddr *name, int *namelen);
    If ‘s’ is a socket that was bound in the past, this function will write the address it was bound to into the ‘name’ and ‘namelen’ arguments.
  25. unsigned long int htonl(unsigned long int hostlong);
    unsigned short int htons(unsigned short int hostshort);
    unsigned long int ntohl(unsigned long int netlong);
    unsigned short int ntohs(unsigned short int netshort);

    Converts integers from the local architecture into network byte order. All integers sent over networks should be in this format, and port numbers given to bind(), connect() and accept() should be in this order as well. In order of appearance, the functions are host to network long (4 bytes), host to network short (2 bytes), network to host long, and network to host short. These functions are part of the XDR standard.
  26. struct hostent *gethostbyname(const char *name);
    struct hostent *gethostbyaddr(const char *addr, int len, int type);

    Retrieve information about a remote computer using either its name (“www.yahoo.com”) or its address (as we have seen, several address types exist).
    The hostent structure is defined in <netdb.h> as follows:struct hostent {
    char *h_name; /* official name of host */
    char **h_aliases; /* alias list */
    int h_addrtype; /* host address type, AF_INET at present */
    int h_length /* The length of the address in bytes.
    char **h_addr_list; /* zero-terminated array of hosts’ network
    addresses in network byte order. */
    char *h_addr; /* First address in h_addr_list */
    }
  27. When using gethostbyname() or gethostbyaddr(), the variable errno and the function perror() won’t work. The two functions will return NULL on error, but in order to know which error occurred and to print an appropriate error message, replace errno and perror by the following (which do the same):
    extern int h_errno;
    void herror(const char *s);