Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linux best way in two-way IPC in C

Tags:

c

linux

I haved a problem to complete my example when asked this question.
I searched for ways of implementing IPC in Google.
I can't decide which way is best for write my program.
I tried lots of implementations and have lots of complications with them.

I want to have:
1. parent process to manage child process - OK ( template )
2. parent and children must have implemented callback for new message signal
3. one process do not know message size from other process ( char * )

My code:

header.h:

#ifndef MESSAGES_H
#define MESSAGES_H

#include <stdio.h>
#include <stdlib.h>

// need here: some includes and definitions

inline char * read_message( /* need here: some params */ ) {
    // need here: read message function
}

inline char * send_message( /* need here: some params */ ) {
    // need here: send message function
}
#endif

parent.c:

#include "header.h"

// parent specyfic includes and definitions

void on_message( /* need here: some parameters */ ) {
    char *message = read_message( /* need here: some other parameters */ );
    // do something with / if message etc.
}

int runChild(key) {
    int pid = fork();
    if (pid == 0) {
        execl("./child", "./child", /* params here */, null);
    }else{
        return pid;
    }
}

int main(int argc, char *argv[]) {
    // need here: prepare IPC
    // need here: on new message event call function "on_message"
    int childPid = runChild(key);
    // loop - for example: gtk_main()
    // need here: close childs
}

child.c

#include "header.h"

// child specyfic includes and definitions

void on_message( /* need here: some parameters */ ) {
    char *message = read_message( /* need here: some other parameters */ );
    // do something with / if message etc.
}

int main(int argc, char *argv[]) {
    // need here: prepare IPC
    // need here: on new message event call function "on_message"
    int pid = getpid();
    int parentPid = getppid();
    printf("Child with pid %d is ready for messages from parent with pid: %d", pid, parentPid);
    // event loop - for example: gtk_main()
}

Which IPC way is better in that example program template ( safe and speed ) ? Can you share a really simple example that matches the above template ?

like image 904
s77s Avatar asked Dec 08 '14 23:12

s77s


People also ask

What is the fastest IPC method in Linux?

Shared memory is the fastest form of interprocess communication. The main advantage of shared memory is that the copying of message data is eliminated. The usual mechanism for synchronizing shared memory access is semaphores.

What are the two types of IPC techniques?

Synchronization in Interprocess Communication The two types of semaphores are binary semaphores and counting semaphores.

How is IPC handled in Linux?

Processes communicate with each other and with the kernel to coordinate their activities. Linux supports a number of Inter-Process Communication (IPC) mechanisms. Signals and pipes are two of them but Linux also supports the System V IPC mechanisms named after the Unix TM release in which they first appeared.

What are the different IPC mechanisms supported by Linux?

Linux supports three types of interprocess communication mechanisms which first appeared in Unix System V (1983). These are message queues, semaphores and shared memory.


2 Answers

There are a number of different ways to implement IPC. For a good comparison, see Stevens' books. The classic is 'Advanced Programming in the UNIX environment', but there is also 'UNIX Network Programming, Volume 2, Second Edition: Interprocess Communications'. I know it is sometimes not considered good form to point to references elsewhere, but whether this is an academic or commercial problem, most UNIX programmers would recognise Stevens as an invaluable resource.

That said, here are your main options for IPC:

  1. Use a pipe() between the processes. The format will always be stream-based; if you are sending datastructures, this can be a pain as you need to worry not only about serialization, but also about buffering and translating the 'packets' back into messages. Pipes are unidirectional, so you will need two for bidirectional communication.

  2. Use a named pipe or fifo. This allows many-to-one communication and for the fifo to persist after one end has quit. Otherwise as per (1).

  3. Use a socketpair between the processes - specifically a unix domain socket. Sockets are bidirectional. You can either use streaming sockets (SOL_STREAM), datagrams (unreliable, ordering not guaranteed - SOCK_DGRAM) or perhaps better sequenced reliable bidirectional packet communication (SOCK_SEQPACKET). Packet based communication means that you can (e.g.) put one datastructure in each packet.

  4. Use signals. Effectively you get to send one integer at a time. Signals do not mix well with threading, handling interrupted system calls is hard, and various race conditions make them unreliable unless you know what you are doing and are not too worried about portability. Best avoided in most cases.

  5. Use system V semaphores (semget etc.) or POSIX semaphores (sem_open etc.). Useful for sending signals between processes to achieve synchronization but not much more.

  6. Use shared memory (shmget etc.) - the same page(s) are made visible to multiple processes. You will need to combine with some method of synchronisation.

  7. System V message queues (msgget etc.) - maintain a queue of packets (messages) between two processes.

  8. Some combination of the above.

I've omitted some things only in forks of the kernel (e.g. Binder) or under development (e.g. KDBus).

Examples of and tutorials for nearly all the above can be found here.

Now, most of these could be used for the application you mention. It looks like you want to send variable size messages, so if you use a stream based protocol, the normal hack is to send the length of the packet as the first 1, 2 or 4 bytes (depending on the maximum length). Packet based protocols are easier here (obviously) but each have their own maximum packet size. Do you care about reliability? Do you care about portability? Do you care about speed? All of these are valid concerns when choosing between them.

Finally, an advantage to the FD-based methods (e.g. pipes, socketpairs) is that you can add them to a normal select() loop; if you have other things going on in your program, this may be helpful.

You asked in comments for some examples of socketpair code. I reiterate the comment at the top re getting hold of Stephens. In the absence of that:

  • Socketpair() in C/Unix shows a good example of setting up a socketpair for IPC using fork().
  • The tutorial mentioned above has a good section on socketpair() here.
like image 125
abligh Avatar answered Sep 23 '22 10:09

abligh


Here's some setup code from a multiprocess program I recently wrote, with the use of select to provide non-blocking waiting. This also apparently is one of the better ways to do it in C++ because from what I gather file descriptors aren't well supported by the standard library...

// Parent
int main(int argc, char **argv) {

    // Pipe, fork, exec (to run robot in child)
    int toParent[2], fromParent[2];
    pipe(toParent);
    pipe(fromParent);

    // Redirect childs stdin/stdout
    if (fork()) { // parent
        close(toParent[1]); // [1] == write
        close(fromParent[0]); // [0] == read
    }
    else {
        close(toParent[0]);
        close(fromParent[1]);
        dup2(toParent[1], 1);
        dup2(fromParent[0], 0);
        close(toParent[1]);
        close(fromParent[0]);
        execl("../robot/robot", "../robot/robot", (char *) NULL);
    }


    FILE * output = fdopen(fromParent[1], "w");
    FILE * input = fdopen(toParent[0], "r");

    // Set up for select() read of input pipe
    fd_set set;
    struct timeval timeout;

    // Initialize the file descriptor set.
    FD_ZERO(&set);
    FD_SET(toParent[0], &set);

    // Initialize the timeout data structure
    timeout.tv_sec = 0;
    timeout.tv_usec = 10;

    while(1) {
        // Non-blocking read of pipe
        // NOTE: only expecting to read one pipe so no need to check which pipe got data
        if (select(toParent[0]+1, &set, NULL, NULL, &timeout) > 0) {
            // read the input pipe here
        }
        // Reset select FD -- maybe only do this when an input has been read?
        FD_ZERO(&set);
        FD_SET(toParent[0],&set);
}

The general idea is in to allow the child to communicate to the parent through its stdin/stdout (by using dup2()), and then use the FILE *output and input to write to child.The only caveat is that debug prints to stdout in child might cause unexpected behaviour if the parent isn't dealing with it, so in general its safest to print debug messages to stderr in child.

As for the callbacks, you can use select, which is something that is pretty well documented elsewhere.

like image 36
chris Avatar answered Sep 23 '22 10:09

chris