概述
Inter Process Communication(IPC)是指在作为程序执行单位的进程之间进行的数据交换。进程的依赖由操作系统管理,以便尽可能松散耦合。因此,IPC必须通过Linux操作系统的功能完成。操作系统提供给进程的数据交换方法不止一种。提供了各具特色的丰富多彩的方法。
这里介绍的有以下5个。
- 共享内存
- 信号量
- Map内存
- 管道(Pipe)
- socket 通信
共享内存
优点
进程之间共享相同的内存。共享内存的最大优势在于其访问速度。一旦生成共享内存,就可以在不使用内核功能的情况下进行访问,因此可以以与进程中的普通内存相同的速度进行访问。这种方法经常用于需要处理性能的软件,如需要这种访问速度的好处。
缺点
另一方面,如果从两个进程同时写入,则会发生冲突。共享内存不包含防止这种冲突的互斥机制。
需要自己准备。
例子
在process_a中确保共享内存字符串“Hello World!”写入共享内存。写入共享内存的数据由process_b读取并打印到控制台上。
- 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 ()
{
/* 段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;
}
/* 段的分配 */
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;
}
/* 显示密钥和片段ID */
std::cout << "キー:" << key << std::endl;
std::cout << "セグメントID:" << segment_id << std::endl;
/* 附加到共享内存 */
char* const shared_memory = reinterpret_cast<char*>(shmat(segment_id, 0, 0));
printf ("shared memory attached at address %p\n", shared_memory);
/* 写入共享存储器 */
sprintf (shared_memory, "Hello, world.");
std::cout << "Hit any key when ready to close shared memory" << std::endl;
std::cin.get();
/* 分离共享内存 */
shmdt(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 ()
{
/* 段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;
}
/* 附加到共享内存 */
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);
/* 读取共享内存 */
printf ("%s\n", shared_memory);
/* 分离共享内存 */
shmdt(shared_memory);
return EXIT_SUCCESS;
}
涉及进程间通信的Linux命令
介绍创建进程间通信程序所需的命令。
ipcs命令
显示进程间通信的状态。
kei@Glou-Glou:~/src/ipc/shared_memory$ ipcs
------ Message Queues --------
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x0052e2c1 0 postgres 600 56 6
------ Semaphore Arrays --------
key semid owner perms nsems
ipcrm命令
释放确保的共享资源。
kei@Glou-Glou:~/src/ipc/shared_memory$ ipcrm shm <shmid>
信号量
信号量在没有父子关系的进程之间共享整数类型的数据。它有一种机制来控制多个进程的同时访问,但它的缺点是只能处理整数类型的数据。主要被使用在共享内存的排他处理上。
例子
- 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()
{
/* 确保信号量的安全 */
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;
}
/* 信号量的初始化 */
union semun argument;
unsigned short values[1];
values[0] = 1;
argument.array = values;
semctl(sem_id, 0, SETALL, argument);
/* 等待进程B的执行 */
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);
/* 信号量的解放 */
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()
{
/* 确保信号量的安全 */
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信号 */
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;
}
Map内存
多个进程通过文件进行通信。在访问文件时执行内存映射以加快处理速度。内存映射是指将文件、设备等映射到虚拟地址空间,使其可以像内存一样访问。这允许您将数据放置在文件中,而无需序列化。
例子
- 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(2)调用创建的。Pipe(2)将两个文件描述符返回到参数数组。fork后,两个进程使用p[0]读取,并使用p[1]写入。该过程实际上是由它们管理的循环缓冲区之间的读取和写入。
使用fork(2),每个进程打开的文件表被复制,所以每个进程都有两个读取器和两个写入器。为了使管道正常工作,您必须关闭额外的读取器和写入器。
例如,如果另一个读取器的结尾也被打开以通过相同的进程写入,则文件结束的指示不会返回。以下代码显示了管道创建、fork和重复管道结束的清除。
#include <stdio.h>
#include <unistd.h>
...
int p[2];
...
if (pipe(p) == -1) exit(1);
switch( fork() )
{
case 0: /* in child */
close( p[0] );
dup2( p[1], 1);
close P[1] );
exec( ... );
exit(1);
default: /* in parent */
close( p[1] );
dup2( P[0], 0 );
close( p[0] );
break;
}
...
显示了在一定条件下从管道读取和写入管道的结果。
管道读取和写入的结果
项目 | 条件 | 结果 |
---|---|---|
读入 | 空管道,写入器连接 | 读取将被阻塞 |
写入 | 完整的管道,写入器连接 | 写入将被阻塞 |
读入 | 空管道,无连接写入器 | EOF被送回 |
写入 | 没有读入 | SIGPIPE |
如果将fcntl(2)调用到描述符并设置FNDELAY,则可以阻止阻塞。如果在此状态下调用输入/输出函数,则errno将设置EWOULDBLOCK,并返回错误(-1)。
FIFO
又名named piped。在文件系统上具有名称的管道。没有父子关系,所有进程都可以创建、访问和删除FIFO。这个FIFO也可以通过mkfifo命令从控制台创建。
$ mkfifo ./fifo.tmp
创建的FIFO可以像普通文件一样访问。
- FIFO写入
$ cat > fifo.tmp
- FIFO读入
$ cat fifo.tmp
以下是在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()
{
// 文件描述符
int fd;
// FIFO作成
// mkfifo(<pathname>, <permission>)
mkfifo("/tmp/myfifo", 0666);
std::string message("Hello World!");
// 在只写打开FIFO
fd = open("/tmp/myfifo", O_WRONLY);
// 消息的写入
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()
{
// 文件描述符
int fd;
// FIFO作成
// mkfifo(<pathname>, <permission>)
mkfifo("/tmp/myfifo", 0666);
char str[BUFFER_SIZE];
// 在只读打开FIFO
fd = open("/tmp/myfifo", O_RDONLY);
read(fd, str, BUFFER_SIZE);
const std::string message(str);
// 读取内容的显示
std::cout << message << std::endl;
close(fd);
return EXIT_SUCCESS;
}
FIFO可以从多个进程读取和写入。多个同时访问将自动排他处理。
socket通讯
socket实现不依赖进程之间的通信。此外,还有其他方法没有的好处。它可以与其他机器上的进程进行通信。以下是使用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;
/* socket作成 */
socket_fd = socket(PF_LOCAL, SOCK_STREAM, 0);
/* 设置为服务器 */
name.sun_family = AF_LOCAL;
strcpy(name.sun_path, socket_name.c_str());
bind(socket_fd, reinterpret_cast<sockaddr*>(&name), SUN_LEN (&name));
/* 打开socket */
listen(socket_fd, 5);
/* 等待,直到连接到消息 */
do {
sockaddr_un client_name;
socklen_t client_name_len;
int client_socket_fd;
/* 等待直到有连接 */
client_socket_fd = accept(socket_fd, reinterpret_cast<sockaddr*>(&client_name), &client_name_len);
/* 收到信息 */
client_sent_quit_message = server(client_socket_fd);
/* 切断*/
close(client_socket_fd);
} while (!client_sent_quit_message);
/* 关闭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;
/* socket作成 */
socket_fd = socket(PF_LOCAL, SOCK_STREAM, 0);
/* 设置socket名 */
name.sun_family = AF_LOCAL;
strcpy(name.sun_path, socket_name.c_str());
/* 连接socket */
connect(socket_fd, reinterpret_cast<sockaddr*>(&name), SUN_LEN (&name));
/* 发送消息 */
write_text(socket_fd, message);
close(socket_fd);
return EXIT_SUCCESS;
}