Ipc (Pipe and Message Queue)
Ipc (Pipe and Message Queue)
There are several methods of IPC available to Linux C programmers: Pipes message queues Semaphore sets Shared memory segments Networking sockets (Berkeley style) Full-duplex pipes (STREAMS pipes) These facilities, when used effectively, provide a solid framework for client/server development on any UNIX system (including Linux).
Pipes :
Basic Concepts Simply put, a pipe is a method of connecting the standard output of one process to the standard input of another. Pipes are the eldest of the IPC tools, having been around since the earliest incarnations of the UNIX operating system. They provide a method of one-way communications (hence the term half-duplex) between processes. This feature is widely used, even on the UNIX command line (in the shell). ls | sort | head -5 When a process creates a pipe, the kernel sets up two file descriptors for use by the pipe. One descriptor is used to allow a path of input into the pipe (write), while the other is used to obtain data from the pipe (read). At this point, the pipe is of little practical use, as the creating process can only use the pipe to communicate with itself. Consider this representation of a process and the kernel after a pipe has been created: From the above diagram, it is easy to see how the descriptors are connected together. If the process sends data through the pipe (fd0), it has the ability to obtain (read) that information from pipe(fd1). However, there is a much larger objective of the simplistic sketch above. While a pipe initially connects a process to itself, data traveling through the pipe moves through the kernel. At this point, the pipe is fairly useless. After all, why go to the trouble of creating a pipe if we are only going to talk to ourselves? At this point, the creating process typically forks a child process. Since a child process will inherit any open file descriptors from
the parent, we now have the basis for multiprocess communication (between parent and child). Consider this updated version of our simple sketch: Above, we see that both processes now have access to the file descriptors which constitute the pipeline. It is at this stage, that a critical decision must be made. In which direction do we desire data to travel? Does the child process send information to the parent, or vice versa? The two processes mutually agree on this issue, and proceed to close the end of the pipe that they are not concerned with. For discussion purposes, lets say the child performs some processing, and sends information back through the pipe to the parent. Our newly revised sketch would appear as such: HALF-DUPLEX LINUX PIPES Construction of the pipeline is now complete! The only thing left to do is make use of the pipe. To access a pipe directly, the same system calls that are used for low-level file I/O can be used (recall that pipes are actually represented internally as a valid inode). To send data to the pipe, we use the write() system call, and to retrieve data from the pipe, we use the read() system call. Remember, low-level file I/O system calls work with file descriptors.!
Creating Pipes in C:
Creating pipelines with the C programming language can be a bit more involved than our simple shell example. To create a simple pipe with C, we make use of the pipe() system call. It takes a single argument, which is an array of two integers, and if successful, the array will contain two new file descriptors to be used for the pipeline. After creating a pipe, the process typically spawns a new process (remember the child inherits open file descriptors). SYSTEM CALL: pipe(); PROTOTYPE: int pipe( int fd[2] ); RETURNS: 0 on success -1 on error: errno NOTES: fd[0] is set up for reading, fd[1] is set up for writing The first integer in the array (element 0) is set up and opened for reading, while the second integer (element 1) is set up and opened for writing. Visually speaking, the output
of fd1 becomes the input for fd0. Once again, all data traveling through the pipe moves through the kernel. #include <stdio.h> #include <unistd.h> #include <sys/types.h> main() { int fd[2]; pipe(fd); . . } Remember that an array name in C decays into a pointer to its first member. Above, fd is equivalent to &fd[0]. Once we have established the pipeline, we then fork our new child process: #include <stdio.h> #include <unistd.h> #include <sys/types.h> main() { int fd[2]; pid_t childpid; pipe(fd); if((childpid = fork()) == -1) { perror("fork"); exit(1); } . . } If the parent wants to receive data from the child, it should close fd1, and the child should close fd0. If the parent wants to send data to the child, it should close fd0, and the child should close fd1. Since descriptors are shared between the parent and child, we should always be sure to close the end of pipe we arent concerned with. On a technical note, the EOF will never be returned if the unnecessary ends of the pipe are not explicitly closed. #include <stdio.h> #include <unistd.h>
#include <sys/types.h> main() { int fd[2]; pid_t childpid; pipe(fd); if((childpid = fork()) == -1) { perror("fork"); exit(1); } if(childpid == 0) { /* Child process closes up input side of pipe */ close(fd[0]); } else { /* Parent process closes up output side of pipe */ close(fd[1]); } . . } As mentioned previously, once the pipeline has been established, the file descriptors may be treated like descriptors to normal files.
Pipe1.c
#include <stdio.h> #include <unistd.h> #include <sys/types.h> int main(void) { int fd[2], nbytes; pid_t childpid; char string[] = "Hello, world!\n"; char readbuffer[80]; pipe(fd); if((childpid = fork()) == -1) { perror("fork"); exit(1); } if(childpid == 0) {
/* Child process closes up input side of pipe */ close(fd[0]); /* Send "string" through the output side of pipe */ write(fd[1], string, strlen(string)); exit(0); } else { /* Parent process closes up output side of pipe */ close(fd[1]); /* Read in a string from the pipe */ nbytes = read(fd[0], readbuffer, sizeof(readbuffer)); printf("Received string: %s", readbuffer); } return(0); } Often, the descriptors in the child are duplicated onto standard input or output. The child can then exec() another program, which inherits the standard streams.
Consider this example, which opens up a pipe to the sort command, and proceeds to sort an array of strings: /***************************************************************************** MODULE: popen1.c *****************************************************************************/ #include <stdio.h> #define MAXSTRS 5 int main(void) { int cntr; FILE *pipe_fp; char *strings[MAXSTRS] = { "echo", "bravo", "alpha", "charlie", "delta"}; /* Create one way pipe line with call to popen() */ if (( pipe_fp = popen("sort", "w")) == NULL) { perror("popen"); exit(1); } /* Processing loop */ for(cntr=0; cntr<MAXSTRS; cntr++) { fputs(strings[cntr], pipe_fp); fputc(\n, pipe_fp);
} /* Close the pipe */ pclose(pipe_fp); return(0); } Since popen() uses the shell to do its bidding, all shell expansion characters and metacharacters are available for use! In addition, more advanced techniques such as redirection, and even output piping, can be utilized with popen(). MODULE: popen2.c *****************************************************************************/ #include <stdio.h> int main(void) { FILE *pipein_fp, *pipeout_fp; char readbuf[80]; /* Create one way pipe line with call to popen() */ if (( pipein_fp = popen("ls", "r")) == NULL) { perror("popen"); exit(1); } /* Create one way pipe line with call to popen() */ if (( pipeout_fp = popen("sort", "w")) == NULL) { perror("popen"); exit(1); } /* Processing loop */ while(fgets(readbuf, 80, pipein_fp)) fputs(readbuf, pipeout_fp); /* Close the pipes */ pclose(pipein_fp); pclose(pipeout_fp); return(0); } For our final demonstration of popen(), lets create a generic program that opens up a pipeline between a passed command and filename: MODULE: popen3.c *****************************************************************************/ #include <stdio.h> int main(int argc, char *argv[]) { FILE *pipe_fp, *infile; char readbuf[80];
if( argc != 3) { fprintf(stderr, "USAGE: popen3 [command] [filename]\n"); exit(1); } /* Open up input file */ if (( infile = fopen(argv[2], "rt")) == NULL) { perror("fopen"); exit(1); } /* Create one way pipe line with call to popen() */ if (( pipe_fp = popen(argv[1], "w")) == NULL) { perror("popen"); exit(1); } /* Processing loop */ do { fgets(readbuf, 80, infile); if(feof(infile)) break; fputs(readbuf, pipe_fp); } while(!feof(infile)); fclose(infile); pclose(pipe_fp); return(0); } Try this program out, with the following invocations: popen3 sort popen3.c popen3 cat popen3.c popen3 more popen3.c popen3 cat popen3.c | grep main
Atomic Operations with Pipes In order for an operation to be considered atomic, it must not be interrupted for any reason at all. The entire operation occurs at once. The POSIX standard dictates in /usr/include/posix1 lim.h that the maximum buffer size for an atomic operation on a pipe is: #define _POSIX_PIPE_BUF 512 Up to 512 bytes can be written or retrieved from a pipe atomically. Anything that crosses this threshold will be split, and not atomic. Under Linux, however, the atomic operational limit is defined in linux/limits.h as:
#define PIPE_BUF
4096
A named pipe works much like a regular pipe, but does have some noticeable differences. Named pipes exist as a device special file in the file system. When all I/O is done by sharing processes, the named pipe remains in the file system for later use. Creating a FIFO There are several ways of creating a named pipe. The first two can be done directly from the shell. mknod MYFIFO p mkfifo a=rw MYFIFO The above two commands perform identical operations, with one exception. The mkfifo command provides a hook for altering the permissions on the FIFO file directly after creation. With mknod, a quick call to the chmod command will be necessary. FIFO files can be quickly identified in a physical file system by the p indicator seen here in a long directory listing: $ ls -l MYFIFO prw-r--r-1 root root 0 Dec 14 22:15 MYFIFO| To create a FIFO in C, we can make use of the mknod() system call: LIBRARY FUNCTION: mknod(); PROTOTYPE: int mknod( char *pathname, mode_t mode, dev_t dev); RETURNS: 0 on success, -1 on error: errno = EFAULT (pathname invalid) EACCES (permission denied) ENAMETOOLONG (pathname too long) ENOENT (invalid pathname) ENOTDIR (invalid pathname) (see man page for mknod for others) NOTES: Creates a filesystem node (file, device file, or FIFO) I/O operations on a FIFO are essentially the same as for normal pipes, with once major exception. An open system call or library function should be used to physically open up a channel to the pipe. With half-duplex pipes, this is unnecessary, since the pipe resides in the kernel and not on a physical filesystem. In our examples, we will treat the pipe as a stream, opening it up with fopen(), and closing it with fclose().
Consider a simple server process: ***************************************************************************** MODULE: fifoserver.c *****************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <unistd.h> #include <linux/stat.h> #define FIFO_FILE "MYFIFO" int main(void) { FILE *fp; char readbuf[80]; /* Create the FIFO if it does not exist */ umask(0); mknod(FIFO_FILE, S_IFIFO|0666, 0); while(1) { fp = fopen(FIFO_FILE, "r"); fgets(readbuf, 80, fp); printf("Received string: %s\n", readbuf); fclose(); } return(0); } Since a FIFO blocks by default, run the server in the background after you compile it: $ fifoserver& We will discuss a FIFOs blocking action in a moment. First, consider the following simple client frontend to our server:
*****************************************************************************/ MODULE: fifoclient.c *****************************************************************************/ #include <stdio.h> #include <stdlib.h> #define FIFO_FILE "MYFIFO" int main(int argc, char *argv[]) { FILE *fp; if ( argc != 2 ) { printf("USAGE: fifoclient [string]\n"); exit(1);
} if((fp = fopen(FIFO_FILE, "w")) == NULL) { perror("fopen"); exit(1); } fputs(argv[1], fp); fclose(); return(0); } pipes must have a reader and a writer. If a process tries to write to a pipe that has no reader, it will be sent the SIGPIPE signal from the kernel. This is imperative when more than two processes are involved in a pipeline. The ipcs Command The ipcs command can be used to obtain the status of all System V IPC objects. The Linux version of this tool was also authored by Krishna Balasubramanian. ipcs -q: Show only message queues ipcs -s: Show only semaphores ipcs -m: Show only shared memory ipcs --help: Additional arguments By default, all three categories of objects are shown. Consider the following sample output of ipcs: ------ Shared Memory Segments -------shmid owner perms bytes nattch status ------ Semaphore Arrays -------semid owner perms nsems status ------ Message Queues -------msqid owner perms used-bytes messages 0 root 660 5 1 Here we see a single message queue which has an identifier of 0. It is owned by the user root, and has octal permissions of 660, or -rw-rw---. There is one message in the queue, and that message has a total size of 5 bytes.
having a message type of 1, request messages could be type 2, etc. The possibilities are endless. On another note, do not be misled by the almost too-descriptive name assigned to the message data element (mtext). This field is not restricted to holding only arrays of characters, but any data, in any form. The field itself is actually completely arbitrary, since this structure gets redefined by the application programmer. Consider this redefinition: struct my_msgbuf { long mtype; /* Message type */ long request_id; /* Request identifier */ struct client info; /* Client information structure */ }; Here we see the message type, as before, but the remainder of the structure has been replaced by two other elements, one of which is another structure! This is the beauty of message queues. The kernel makes no translations of data whatsoever. Any information can be sent. There does exist an internal limit, however, of the maximum size of a given message. In Linux, this is defined in linux/msg.h as follows: #define MSGMAX 4056 /* <= 4056 */ /* max size of message (bytes) */ Messages can be no larger than 4,056 bytes in total size, including the mtype member, which is 4 bytes in length (long). Kernel msg structure The kernel stores each message in the queue within the framework of the msg structure. It is defined for us in linux/msg.h as follows: /* one msg structure for each message */ struct msg { struct msg *msg_next; /* next message on queue */ long msg_type; char *msg_spot; /* message text address */ short msg_ts; /* message text size */ }; msg next This is a pointer to the next message in the queue. They are stored as a singly linked list within kernel addressing space. msg type This is the message type, as assigned in the user structure msgbuf. msg spot
A pointer to the beginning of the message body. msg ts The length of the message text, or body. Kernel msqid ds structure Each of the three types of IPC objects has an internal data structure which is maintained by the kernel. For message queues, this is the msqid ds structure. The kernel creates, stores, and maintains an instance of this structure for every message queue created on the system. It is defined in linux/msg.h as follows: /* one msqid structure for each queue on the system */ struct msqid_ds { struct ipc_perm msg_perm; struct msg *msg_first; /* first message on queue */ struct msg *msg_last; /* last message in queue */ time_t msg_stime; /* last msgsnd time */ time_t msg_rtime; /* last msgrcv time */ time_t msg_ctime; /* last change time */ struct wait_queue *wwait; struct wait_queue *rwait; ushort msg_cbytes; ushort msg_qnum; ushort msg_qbytes; /* max number of bytes on queue */ ushort msg_lspid; /* pid of last msgsnd */ ushort msg_lrpid; /* last receive pid */ };
mesg1.c
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include<sys/msg.h> main() { int msqid; key_t key=15; msqid=msgget(key,IPC_CREAT|0644); if (msqid<0) perror("msgget failed!"); else printf("\nMessage queue created successfullywith key %d \n",msqid); }
For Client Program Step 1:Start Step 2:Create a structure that contains the message type and the message that will be send to the server. Step 3:Create a buffer. Step 4:Initialize a key. Step 5:Assign running=true. Step 6:If(running=true) then go to step-7 else go to step-9. Step 7:Get the message from the user. Step 8:If(message=end) then assign running=false and copy the message into the buffer and assign message type=1 and send it to the server then go to step-6 else copy the message into the buffer and assign message type=1 and send it to the server then go to step-6. Step 9:End
For Server Program Step 1:Start Step 2:Create a structure that contains the message type and the message that will be received from the client. Step 3:Initialize a key. Step 4:Assign running=true. Step 5:If(running=true) then go to step-6 else go to step-8. Step 6:Receive the message from the client and print it. Step 7:If(message=end),then assign running=false and go to step-5 else go to step-5. Step 8:End
Client Program in C #include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<unistd.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/msg.h> #define MAX_TEXT 512 sruct my_msg_st { long int my_msg_type; char some_text[MAX_TEXT]; }; int main() { int running=1; struct my_msg_st some_data; int msgid; char buffer[BUFSIZ]; msgid=msgget((key_t)1234,0666 | IPC_CREAT); while(running) { printf("Enter some text : "); fgets(buffer,BUFSIZ,stdin); some_data.my_msg_type = 1; strcpy(some_data.some_text,buffer); if(msgsnd(msgid,(void *)&some_data,MAX_TEXT,0)==-1) { printf("error"); exit(1); } if(strncmp(buffer,"end",3)==0) running=0; } exit(EXIT_SUCCESS); }
Server Program in C #include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<unistd.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/msg.h> struct my_msg_st { long int my_msg_type; char some_text[BUFSIZ]; }; int main() { int running=1; int msgid; struct my_msg_st some_data; long int msg_to_receive=0; msgid=msgget((key_t)1234,0666 | IPC_CREAT); if(msgid==-1) { fprintf(stderr,"msgget failed with error:%d\n",errno); exit(EXIT_FAILURE); } while(running) { if(msgrcv(msgid,(void *)&some_data,BUFSIZ,msg_to_receive,0)==-1) { fprintf(stderr,"msgrcv failed withh error:%d\n",errno); exit(EXIT_FAILURE); } printf("You wrote: %s",some_data.some_text); if(strncmp(some_data.some_text,"end",3)==0) running=0; } if(msgctl(msgid,IPC_RMID,0)==-1) { fprintf(stderr,"msgct1(IPC_RMID) failed \n"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }