简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长!
优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀
人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
1.前言
本篇目的:理解IPC通信消息队列、管道、socket两次内存拷贝与共享内存一次内存拷贝。
2.IPC通信消息队列、管道、socket、共享内存介绍
- 在Linux和Android中,进程间通信(IPC)的方式有很多种,包括管道(Pipe)、消息队列(Message Queue)、共享内存(Shared Memory)、信号量(Semaphore)、套接字(Socket)等。其中,管道、消息队列、套接字都需要进行两次内存拷贝,而共享内存只需要一次内存拷贝。
1. 两次内存拷贝的IPC方式:
-
管道(Pipe): 当一个进程向管道中写入数据时,数据首先会被拷贝到内核缓冲区,然后当另一个进程从管道中读取数据时,数据会从内核缓冲区拷贝到该进程的内存空间。这就是两次内存拷贝。
-
消息队列(Message Queue): 消息队列的工作方式与管道类似,数据在发送和接收时都需要经过内核缓冲区,因此也需要两次内存拷贝。
-
套接字(Socket): 套接字在发送和接收数据时,数据也需要经过内核缓冥区,因此也需要两次内存拷贝。
2. 一次内存拷贝的IPC方式:
- 共享内存(Shared Memory): 共享内存是最快的IPC方式,因为进程是直接对内存进行操作,数据不需要在进程和内核之间进行拷贝。但是,由于多个进程可以同时操作同一块内存,因此需要使用信号量等同步机制来防止数据的不一致。
3.总结
-
一次内存拷贝:
一次内存拷贝是指数据在用户空间和内核空间之间只进行了一次拷贝。在Linux和Android中,使用共享内存(shared memory)实现IPC通信是一种一次内存拷贝的方式。共享内存允许多个进程共享同一块物理内存,因此数据被写入内核空间后,其他进程可以直接在内存中读取数据,避免了多次拷贝的开销。 -
两次内存拷贝:
两次内存拷贝指数据在用户空间和内核空间之间进行了两次拷贝。在Linux和Android中,使用Socket、管道(pipe)和消息队列(message queue)进行IPC通信会涉及两次内存拷贝。
3.代码实例
1.共享内存进行一次内存拷贝
#include <iostream>
#include <sys/shm.h>
#include <cstring>
int main() {
key_t key = ftok("/tmp", 'S');
int shmid = shmget(key, 1024, 0666 | IPC_CREAT);
char *data = (char*)shmat(shmid, nullptr, 0);
std::string message = "Hello, IPC using shared memory!";
std::memcpy(data, message.c_str(), message.size());
shmdt(data);
shmctl(shmid, IPC_RMID, nullptr);
return 0;
}
2.Socket进行两次内存拷贝
// 服务器端
#include <iostream>
#include <sys/socket.h>
#include <cstring>
int main() {
int server_sock = socket(AF_INET, SOCK_STREAM, 0);
// 绑定和监听
int client_sock = accept(server_sock, nullptr, nullptr);
std::string message = "Hello, IPC using sockets!";
send(client_sock, message.c_str(), message.size(), 0);
close(client_sock);
close(server_sock);
return 0;
}
// 客户端
#include <iostream>
#include <sys/socket.h>
#include <cstring>
int main() {
int client_sock = socket(AF_INET, SOCK_STREAM, 0);
// 连接服务器
char buffer[1024];
recv(client_sock, buffer, sizeof(buffer), 0);
std::cout << "Message received from server: " << buffer << std::endl;
close(client_sock);
return 0;
}
3.管道进行两次内存拷贝
#include <iostream>
#include <unistd.h>
#include <cstring>
int main() {
int pipe_fds[2];
if (pipe(pipe_fds) == -1) {
std::cerr << "Failed to create pipe" << std::endl;
return 1;
}
const char* message = "Hello, IPC using pipes!";
const size_t len = strlen(message);
if (write(pipe_fds[1], message, len) != static_cast<ssize_t>(len)) {
std::cerr << "Failed to write to pipe" << std::endl;
return 1;
}
char buffer[64];
ssize_t bytes_read = read(pipe_fds[0], buffer, sizeof(buffer));
if (bytes_read == -1) {
std::cerr << "Failed to read from pipe" << std::endl;
return 1;
}
std::cout << "Message read from pipe: " << std::string(buffer, bytes_read) << std::endl;
close(pipe_fds[0]);
close(pipe_fds[1]);
return 0;
}
4.消息队列进行两次内存拷贝
#include <iostream>
#include <cstring>
#include <sys/msg.h>
struct Message {
long type;
char text[100];
};
int main() {
key_t key = ftok("/tmp", 'Q');
int msgid = msgget(key, 0666 | IPC_CREAT);
Message message;
message.type = 1;
std::strcpy(message.text, "Hello, IPC using message queues!");
msgsnd(msgid, &message, sizeof(message.text), 0);
msgrcv(msgid, &message, sizeof(message.text), 1, 0);
std::cout << "Message received from message queue: " << message.text << std::endl;
msgctl(msgid, IPC_RMID, nullptr);
return 0;
}
5.信号量进行两次内存拷贝
#include <iostream>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstring>
int main() {
key_t key = ftok("/tmp", 'S');
int semid = semget(key, 1, IPC_CREAT | 0666);
if (semid == -1) {
std::cerr << "Failed to create semaphore" << std::endl;
return 1;
}
sembuf acquire = {0, -1, SEM_UNDO};
sembuf release = {0, 1, SEM_UNDO};
if (semop(semid, &acquire, 1) == -1) {
std::cerr << "Failed to acquire semaphore" << std::endl;
return 1;
}
// Critical section - perform operations that require exclusive access
std::string message = "Hello, IPC using semaphores!";
// Perform operations on the shared resource
if (semop(semid, &release, 1) == -1) {
std::cerr << "Failed to release semaphore" << std::endl;
return 1;
}
return 0;
}