Linux interprocess communication

About this article

Introducing Linux IPC (Interprocess Communication).

What is interprocess communication?

Inter Process Communication (IPC) refers to the exchange of data between processes, which are the units of execution of a program. Process dependencies are managed by the OS to be as loosely coupled as possible. Therefore, IPC must be done via the features of the Linux OS. There is more than one way the OS can exchange data for processes. We offer a variety of unique methods. The following five are introduced here.

  1. Shared memory
  2. Semaphore
  3. Mapped memory
  4. Pipe
  5. Socket communication

(If you have any other questions, please let me know in the comments.)

Let's take a look.

shared memory

Share the same memory between processes. The biggest advantage of shared memory is its access speed. Once the shared memory is created, it can be accessed without using kernel features, so it can be accessed at the same speed as normal memory in the process. This method is often used for software that requires processing performance that requires the benefit of this access speed. On the other hand, if you write from two processes at the same time, a conflict will occur. Shared memory does not have a built-in mutual exclusion mechanism to prevent this conflict. You need to prepare it yourself.

Here's how to sample code using shared memory. Allocate shared memory with process_a and write the string "Hello World!" To shared memory. The data written to the shared memory is read by process_b and printed on the console.

process_a.cpp


#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <string>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/ipc.h>

int main ()
{
    /*Generate segment ID*/
    const std::string file_path("./key_data.dat");
    const int id = 42;
    const auto key = ftok(file_path.c_str(), id);
    if(-1 == key) {
        std::cerr << "Failed to acquire key" << std::endl;
        return EXIT_FAILURE;
    }

    /*Segment allocation*/
    const int shared_segment_size = 0x6400;
    const auto segment_id = shmget(key, shared_segment_size,
                                   IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
    if(-1==segment_id) {
        std::cerr << segment_id << " :Failed to acquire segment" << std::endl;
        return EXIT_FAILURE;
    }

    /*Display key and segment ID*/
    std::cout << "Key:" << key << std::endl;
    std::cout << "Segment ID:" << segment_id << std::endl;

    /*Attach to shared memory*/
    char* const shared_memory = reinterpret_cast<char*>(shmat(segment_id, 0, 0));
    printf ("shared memory attached at address %p\n", shared_memory);

    /*Write to shared memory*/
    sprintf (shared_memory, "Hello, world.");


    std::cout << "Hit any key when ready to close shared memory" << std::endl;
    std::cin.get();

    /*Detach shared memory*/
    shmdt(shared_memory);
    /*Free shared memory*/
    shmctl (segment_id, IPC_RMID, 0);

    return EXIT_SUCCESS;
}

process_b.cpp


#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <string>
#include <sys/shm.h>
#include <sys/stat.h>

int main ()
{
    /*Generate segment ID*/
    const std::string file_path("./key_data.dat");
    const int id = 42;
    const auto key = ftok(file_path.c_str(), id);
    if(-1 == key) {
        std::cerr << "Failed to acquire key" << std::endl;
        return EXIT_FAILURE;
    }


    /*Attach to shared memory*/
    const auto segment_id = shmget(key, 0, 0);
    const char* const shared_memory = reinterpret_cast<char*>(shmat(segment_id, 0, 0));
    printf ("shared memory attached at address %p\n", shared_memory);

    /*Reading shared memory*/
    printf ("%s\n", shared_memory);

    /*Detach shared memory*/
    shmdt(shared_memory);

    return EXIT_SUCCESS;
}

Linux commands related to interprocess communication

Introducing the commands required when creating a program for interprocess communication. One is the ipcs command. Displays the status of interprocess communication.

kei@Glou-Glou:~/src/ipc/shared_memory$ ipcs

------Message queue--------
Key msqid Owner authority Number of bytes used Message

------Shared memory segment--------
Key shmid Owner authority byte nattch state
0x00000000 884736     kei        600        16777216   2                       
0x00000000 1409025 kei 600 268435456 2 Target
0x00000000 819202 kei 600 524288 2 Target
       
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~abridgement~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~    

------Semaphore array--------
Key semid owner authority nsems

Next, I will introduce the ipcrm command. Releases reserved shared resources.

kei@Glou-Glou:~/src/ipc/shared_memory$ ipcrm shm <shmid>

semaphore

Semaphores share integer data between processes that have no parent-child relationship. It has a mechanism to control simultaneous access of multiple processes, but the drawback is that it can only handle integer type data. The main use is for mutual exclusion of shared memory. Below is the sample code used by the semaphore.

process_a.cpp


#include<sys/ipc.h>
#include<sys/sem.h>
#include<sys/types.h>
#include<cstdlib>
#include<iostream>

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short int *array;
    struct seminfo *__buf;
};

enum SEMAPHORE_OPERATION
{
    UNLOCK = -1,
    WAIT = 0,
    LOCK = 1,
};

int main()
{
    /*Securing semaphores*/
    const key_t key = 112;
    int sem_flags = 0666;
    int sem_id = semget(key, 1, sem_flags | IPC_CREAT);
    if(-1 == sem_id)
    {
        std::cerr << "Failed to acquire semapore" << std::endl;
        return EXIT_FAILURE;
    }

    /*Initialization of semaphore*/
    union semun argument;
    unsigned short values[1];
    values[0] = 1;
    argument.array = values;
    semctl(sem_id, 0, SETALL, argument);

    /*Wait for process B to execute*/
    std::cout << "Waiting for post operation..." << std::endl;
    sembuf operations[1];
    operations[0].sem_num = 0;
    operations[0].sem_op = WAIT;
    operations[0].sem_flg = SEM_UNDO;
    semop(sem_id, operations, 1);

    /*Release of semaphore*/
    auto result = semctl(sem_id, 1, IPC_RMID, NULL);
    if(-1 == result)
    {
        std::cerr << "Failed to close semaphore" << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

process_b.cpp


#include<sys/ipc.h>
#include<sys/sem.h>
#include<sys/types.h>
#include<cstdlib>
#include<iostream>

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short int *array;
    struct seminfo *__buf;
};

enum SEMAPHORE_OPERATION
{
    UNLOCK = -1,
    WAIT = 0,
    LOCK = 1,
};

int main()
{
    /*Securing semaphores*/
    const key_t key = 112;
    int sem_flags = 0666;
    int sem_id = semget(key, 1, sem_flags);
    if(-1 == sem_id)
    {
        std::cerr << "Failed to acquire semapore" << std::endl;
        return EXIT_FAILURE;
    }

    std::cout << "Unlock semaphore" << std::endl;

    /*Post to semaphore*/
    sembuf operations[1];
    operations[0].sem_num = 0;
    operations[0].sem_op = UNLOCK;
    operations[0].sem_flg = SEM_UNDO;
    semop(sem_id, operations, 1);

    return EXIT_SUCCESS;
}

Mapped memory

Multiple processes communicate over the file. Memory mapping is performed when accessing a file to speed up the process. Memory mapping refers to mapping files, devices, etc. to a virtual address space so that they can be accessed as if they were memory. This allows you to place the data in a file without serialization.

Then, the sample code of interprocess communication using the memory map is shown below.

process_a.cpp


#include<cstdlib>
#include<cstdio>
#include<string>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/stat.h>
#include<unistd.h>

const unsigned int FILE_LENGTH = 0x100;
const std::string FILE_NAME("./data.dat");

int main()
{
    /* Prepare a file large enough to hold an unsigned integer. */
    auto fd = open(FILE_NAME.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    lseek(fd, FILE_LENGTH+1, SEEK_SET);
    write(fd, "", 1);
    lseek(fd, 0, SEEK_SET);
    /* Create the memory mapping. */
    char* const file_memory = reinterpret_cast<char*>(mmap(0, FILE_LENGTH, PROT_WRITE, MAP_SHARED, fd, 0));
    close(fd);
    /* Write a random integer to memory-mapped area. */
    sprintf(file_memory, "%s", "Hello World!");
    /* Release the memory (unnecessary because the program exits). */
    munmap (file_memory, FILE_LENGTH);

    return EXIT_SUCCESS;
}

process_b.cpp


#include<cstdlib>
#include<cstdio>
#include<string>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/stat.h>
#include<unistd.h>

const unsigned int FILE_LENGTH = 0x100;
const std::string FILE_NAME("./data.dat");

int main()
{
    /* Open the file. */
    auto fd = open(FILE_NAME.c_str(), O_RDWR, S_IRUSR | S_IWUSR);
    /* Create the memory mapping. */
    char* const file_memory = reinterpret_cast<char*>(mmap(0, FILE_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
    close(fd);
    /* Read the integer, print it out, and double it. */
    printf("%s\n", file_memory);

    /* Release the memory (unnecessary because the program exits). */
    munmap(file_memory, FILE_LENGTH);

    return EXIT_SUCCESS;
}

pipe

Pipes provide one-way communication between parent-child processes. You will be familiar with it on the command line.

$ ps aux | grep apache

The result of executing ps aux is passed through the pipe|with the grep command. But the tricky part of this pipe is that it requires a parent-child relationship between the processes. The process_a program started the process_b program and tried to create a sample program that exchanges data through a pipe, but it didn't work. If anyone can do it, please let us know in the comments.

FIFO Also known as named piped. A pipe with a name on the file system. All processes can create, access, and delete FIFOs without a parent-child relationship. This FIFO can also be created from the console with the mkfifo command.

$ mkfifo ./fifo.tmp

The created FIFO can be accessed like a normal file.

Write to FIFO


$ cat > fifo.tmp

Read FIFO


$ cat fifo.tmp

Below is a sample code for exchanging data with FIFO.

process_a.cpp


#include <cstdio>
#include <cstdlib>
#include <string>
#include <iostream>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

const size_t BUFFER_SIZE(80);

int main()
{
    //File descriptor
    int fd;

    //Create FIFO
    // mkfifo(<pathname>, <permission>)
    mkfifo("/tmp/myfifo", 0666);

    std::string message("Hello World!");
    //Open FIFO for write-only
    fd = open("/tmp/myfifo", O_WRONLY);

    //Writing a message
    write(fd, message.c_str(), message.size() + 1);
    close(fd);

    return EXIT_SUCCESS;
}

process_b.cpp


#include <cstdio>
#include <cstdlib>
#include <string>
#include <iostream>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

const size_t BUFFER_SIZE(80);

int main()
{
    //File descriptor
    int fd;

    //Create FIFO
    // mkfifo(<pathname>, <permission>)
    mkfifo("/tmp/myfifo", 0666);

    char str[BUFFER_SIZE];
    //Open FIFO read-only
    fd = open("/tmp/myfifo", O_RDONLY);
    read(fd, str, BUFFER_SIZE);
    const std::string message(str);

    //Display of read contents
    std::cout << message << std::endl;
    close(fd);

    return EXIT_SUCCESS;
}

The FIFO can be read and written by multiple processes. Multiple simultaneous accesses are automatically processed exclusively.

Socket communication

Sockets provide communication between independent processes. In addition, there are advantages that other methods do not have. It can communicate with processes on other machines. Below is sample code using a socket.

process_a.cpp


#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cassert>
#include<sys/socket.h>
#include<sys/un.h>
#include<unistd.h>

int server (const int& client_socket)
{
    while (1) {
        const size_t MAX_SIZE = 128;
        char buffer[MAX_SIZE];
        read(client_socket, buffer, MAX_SIZE);
        const std::string message(buffer);
        if(message.size() == 0) {
            return 0;
        } else {
            std::cout << message << std::endl;
            return 1;
        }
    }

    assert(!"This line must not be executed.");
}

int main()
{
    const std::string socket_name("my_socket");
    int socket_fd;
    sockaddr_un name;
    int client_sent_quit_message;
    /*Create socket*/
    socket_fd = socket(PF_LOCAL, SOCK_STREAM, 0);
    /*Set as server*/
    name.sun_family = AF_LOCAL;
    strcpy(name.sun_path, socket_name.c_str());
    bind(socket_fd, reinterpret_cast<sockaddr*>(&name), SUN_LEN (&name));
    /*Open socket*/
    listen(socket_fd, 5);
    /*Wait for a message when connected*/
    do {
        sockaddr_un client_name;
        socklen_t client_name_len;
        int client_socket_fd;
        /*Wait until there is a connection*/
        client_socket_fd = accept(socket_fd, reinterpret_cast<sockaddr*>(&client_name), &client_name_len);
        /*Receive a message*/
        client_sent_quit_message = server(client_socket_fd);
        /*Disconnect*/
        close(client_socket_fd);
    } while (!client_sent_quit_message);
    /*Close socket*/
    close(socket_fd);
    unlink(socket_name.c_str());

    return EXIT_SUCCESS;
}

process_b.cpp


#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<sys/socket.h>
#include<sys/un.h>
#include<unistd.h>

/* Write TEXT to the socket given by file descriptor SOCKET_FD. */
int write_text(const int& socket_fd, const std::string& message)
{
    /* Write the string. */
    write(socket_fd, message.c_str(), message.size() + 1);
    return 0;
}

int main ()
{
    const std::string socket_name("my_socket");
    const std::string message = "Hello World!!";
    int socket_fd;
    sockaddr_un name;
    /*Create a socket*/
    socket_fd = socket(PF_LOCAL, SOCK_STREAM, 0);
    /*Set socket name*/
    name.sun_family = AF_LOCAL;
    strcpy(name.sun_path, socket_name.c_str());
    /*Connect socket*/
    connect(socket_fd, reinterpret_cast<sockaddr*>(&name), SUN_LEN (&name));
    /*Send message*/
    write_text(socket_fd, message);
    close(socket_fd);
    return EXIT_SUCCESS;
}

Conclusion

I have briefly introduced the IPC method provided by Linux. If there are so many methods, you may be wondering which one to use. That is where the programmer's skill is shown. Each method has advantages and disadvantages. Consider and select the best method under the constraints of the software.

References

Recommended Posts

Linux interprocess communication
Types of interprocess communication
Interprocess communication ~ shared memory ~
Interprocess communication for embedded DL
Linux
linux memorandum
Linux command # 4
Linux commands
Linux commands
Linux command # 3
Linux overview
Linux basics
Organize Linux
Linux practice
Ubuntu Linux 20.04
Linux Summary
Linux process
Linux permissions
Tools used to check Linux network communication
Linux command # 5
About Linux
Linux basics
Forgot linux
About Linux
Linux commands
Linux commands
About Linux
About Linux
About Linux ①
Linux redirect