Introducing Linux IPC (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.
(If you have any other questions, please let me know in the comments.)
Let's take a look.
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;
}
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>
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;
}
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;
}
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.
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;
}
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.
How to use shared memory with Linux in C
Example of shared memory I used it as a reference for creating a program.
How to choose the “Key” for inter-processes communication in Linux? I referred to how to generate a key for shared memory.
Programming in C UNIX System Calls and Subroutines using C This was also used as a reference when creating a sample of the shared memory program.
Named Pipe or FIFO with example C program I used it as a reference when creating a FIFO sample program.
Advanced Linux Programming Most of this article is a translation and arrangement of Chapter 5 of this book.
Socket programming in C on Linux – tutorial I used it as a reference for the Socket communication program.
Recommended Posts