LinuxQuestions.org
Review your favorite Linux distribution.
Go Back   LinuxQuestions.org > Blogs > rtmistler
User Name
Password

Notices


Rate this Entry

Using PIPES for Interprocess Communications

Posted 06-01-2013 at 05:46 PM by rtmistler

When you look at Linux documentation, there are a variety of ways to communicate across processes. I've tried a few, http://tldp.org/LDP/tlk/ipc/ipc.html, including signals, semaphores, IPC, shared memory, and PIPES.

My Summary of Each IPC Type

Signals
Signals are singular indicators which carry very one-dimensional information. They are fast, and they are supported by system calls where you can send one easily or wait, blocking or non-blocking to see if you've received a signal. Plus they can be sent to one process or many processes. In short, they are great, but only to convey one item of information. Further, there are two user definable signals, the rest are system defined. So if you need to send a system signal, one that you need to know it occurs and not wait for something else to do it naturally, then you use signals. Or you check signals if you're looking for an event which "may" happen, but only as a protective measure, not because you anticipate it to happen. For instance, the disappearance of a child process which should continue running all the time.

Semaphores
Semaphores are signals more user definable. Yes, and no; actually. There are different types of semaphores, binary and counting are two of these types. Binary is the most useful that I've employed which means you take the semaphore, you got it, you control it and no one else can grab it until you free it back up. The definition of the semaphore is up to you, the use and rules for it are up to you, as is the administration of it. There are system calls you employ to take and give the semaphore, and they allow you to arbitrate ownership so that you can maintain proper control over the processes which are trying to access the same common memory. The semaphore is used to control access to a resource or memory location so that two or more processes/threads may share access without corruption. They are not a real message mechanism, but a controller which helps arbitrate the process.

IPC
I've had very little use of IPC mechanisms, an older version than what we have now likely. However a project I did long ago, did use these where it used the library calls for IPC to create a queue, and allow for message placement and removal. While it was acceptable to have system calls do this, the structure and rules for message contents and sender/receiver, along with rules for who can send when; was up to the coder. In short, it's an acceptable form of communications, but you'll see that I like PIPES more...

Shared Memory
Shared memory is typically used along with signals or semaphores where access to a common memory location is arbitrated by the semaphore or signal. This way the one or more processes can get access to a shared memory location. This is useful when you're passing large amounts of data across your application, without hardware assist; and so instead of copying multiple times, the part which takes the data off an external driver, puts it into shared memory; leaves it there, and then one or more other processes manipulate that data in place, without the need for copying until the data needs to move to somewhere else, either to disk or off-system. For instance routing software may take in a frame, leave it in the same memory and make a determination as to the outgoing point from the system which it will go, with some header modifications along the way.

PIPES
By now you can tell that I like PIPES. What are they? If you redirect input or output in a shell command, you use the PIPE symbol "|" to do so. This is the same thing, the shell creates a temporary resource to transit your data, then gets rid of it once the need for it has gone. You, however do not have to make your pipe temporary, you can make one or several and have them exist for the life of your application.

How do you create a pipe? By using the pipe(2) system call.

Code:
    int pipe2One[2], pipe2Two[2];

    if(pipe(pipe2One) == -1) {
        // error
    }

    if(pipe(pipe2Two) == -1) {
        // error
    }
The pipe(2) system call creates a one direction only communications; hence why my example has two complete pipes. Always the [0] array element in the int array is the receiver, and is also read-only to your process. Always the [1] array element in the int array is the sender, and is also write-only to your process. Therefore to have both send and receive for each process, I merely create two pipes.

At this point if you fork() your process, to create an identical child; the two processes can now communicate using these pipes. As you may guess, each process can use the send and receive side of both of those pipes. Your programming conventions are what maintain the rules of the communications.

Process one reads from the descriptor identified as pipe2One[0], and sends to the descriptor identified as pipe2Two[1].

Process two reads from the descriptor identified as pipe2Two[0], and sends to the descriptor identified as pipe2One[1].

How are these actions performed? By using write(2) and read(2). Here's a more complete example:

Establishment of the pipes and creation of Process Two

Code:
    int pipe2One[2], pipe2Two[2];
    int fl;

    /* Create the pipe to process 1 */
    pipe(pipe2One);

    /* Create the pipe to process 12*/
    pipe(pipe2Two;

    /* Set the RECEIVE side of the PIPE [0] to be non-blocking */
    fcntl(pipe2One[0], F_GETFL, &fl);
    fcntl(pipe2One[0], F_SETFL, (fl | O_NONBLOCK));

    /* Set the TRANSMIT side of the PIPE [1] to be non-blocking */
    fcntl(pipe2One[1], F_GETFL, &fl);
    fcntl(pipe2One[1], F_SETFL, (fl | O_NONBLOCK));

    printf("pipe2One[0]: %d\npipe2One[1]: %d\n", pipe2One[0], pipe2One[1]);

    /* Set the RECEIVE side of the PIPE [0] to be non-blocking */
    fcntl(pipe2Two[0], F_GETFL, &fl);
    fcntl(pipe2Two[0], F_SETFL, (fl | O_NONBLOCK));

    /* Set the TRANSMIT side of the PIPE [1] to be non-blocking */
    fcntl(pipe2Two[1], F_GETFL, &fl);
    fcntl(pipe2Two[1], F_SETFL, (fl | O_NONBLOCK));

    printf("pipe2Two[0]: %d\npipe2Two[1]: %d\n", pipe2Two[0], pipe2Two[1]);

    pid_t pid;

    if((pid = fork()) < 0) {
        printf("fork() error\n");
        return;
    }
    else if(pid == 0) {  // Child - process Two
        run_process_two_loop();
    }
    else {  // Process One
        run_process_one_loop();
    }
Process One Main Loop

Code:
    char read_buf[4096];
    static char out_msg[11] = "Hello There"

    while(1) {
        /* Check for new data */
        memset(read_buf, 0, sizeof(read_buf);
        if(read(pipe2One[0], read_buf, sizeof(read_buf)) > 0) {
            printf("Data to process One: %s\n", read_buf);
        }

        /* Send some data */
        write(pipe2Two[1], out_msg, sizeof(out_msg));

        /* Delay */
        sleep(1);
    }
Process Two Main Loop

Code:
    char read_buf_2[4096];
    static char out_msg_2[11] = "Hi Yourself"

    while(1) {
        /* Check for new data */
        memset(read_buf_2, 0, sizeof(read_buf_2);
        if(read(pipe2Two[0], read_buf_2, sizeof(read_buf_2)) > 0) {
            printf("Data to process Two: %s\n", read_buf_2);
        }

        /* Send some data */
        write(pipe2One[1], out_msg_2, sizeof(out_msg_2));

        /* Delay */
        sleep(1);
    }
The result is until these processes are stopped, they will continue to send messages to each other and print those messages.

Summary
It seems simple, yet involved; where it is simple to create the pipe and use it, involved to determine whether or not blocking is good, and further involved to architect your application processes to employ pipes in a usable manner.

The default setting for a pipe is blocking IO, where you perform a receive and wait there until data arrives, or if the pipe is full, you cannot write to it and will block until you can write. This may make sense for your application, I use select(2) to allow my application to continue but also to check for new data on pipes and other resources.

http://www.linuxquestions.org/questi...-use-it-35530/

As far as the best ways to use pipes, it is your decision. My most common architecture is where I create my peer application processes along with the desired pipes out of a monitoring daemon process. I then start each process main() using only the pipe file descriptors of relevance to that process. In my example of process One and Two above. I would call to start the process One main loop, giving it only the receive descriptor pipe2One[0], and only the transmit descriptor pipe2Two[1]. This way, each process would only employ the resources I designed it to use.

An example of how I create child processes via a daemon:

http://www.linuxquestions.org/questi...ocesses-35540/
Views 819 Comments 0
« Prev     Main     Next »
Total Comments 0

Comments

 

  



All times are GMT -5. The time now is 08:34 AM.

Main Menu
Advertisement
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration