进程间通信(IPC):是指在不同进程之间传播信息,进程只能访问到虚拟空间地址,MMU通过内核为每个进程分配的页表来将虚拟空间地址转换为物理地址进行操作。因此进程之间如果想要通信必须要借助一些方式,如管道(无名管道和命名管道)、消息队列、信号量、内存映射、Socket、Streams等。其中Socket和Streams支持不同主机的两个进程IPC。
一、管道
管道是UNIX最古老的IPC方式。
特点
- 半双工通信,A端读那么B端就只能写,不可改变方向。
- 匿名管道只能在有亲缘关系的进程之间通信(父子进程或兄弟进程之间)
- 它可以看成是一种特殊的文件,因为对于管道操作也是read 和 write操作,但是它本质并不是文件而是内核中的缓冲区,只存在于内存中。
二、FIFO
FIFO,也称为命名管道,是一种文件类型。
特点
- FIFO可以在无关的进程之间进行数据交换,与匿名管道不同。
- FIFO和匿名管道一样都是内存文件,只不过命名管道在磁盘中有一个简单的映像,这个映像大小永远都是0.
- 打开规则:
读进程打开FIFO,并且没有写进程打开时,如果设置了O_NONBLOCK会立刻返回成功。如果没有设置O_NONBLOCK阻塞直到有写进程打开该FIFO - 写进程打开FIFO,并且没有读进程打开时:如果设置了O_NONBLOCK会立刻返回失败,错误码ENXIO。如果没有O_NONBLOCK:阻塞直到有读进程打开该FIFO
- 读取到的数据就会被清除掉。
三、消息队列
消息队列,是保存在内核中的消息链表,消息队列是面向消息通信的,一次读取一条完整的消息,每条消息还包含一个整数表示优先级。进程A往队列写入消息,进程B读取消息,并且经常A写入消息后终止,进程B可以去读取,也就是说消息队列独立于发送和接收进程。
四、信号量
信号量(semaphore)与已经介绍过的IPC结构不同,它是一个计数器,信号量用于实现进程间的互斥和同步操作,而不是存储进程间通信数据。信号量是操作系统提供的一种协调共享资源访问的方法,信号量由内核维护的整形变量。
特点
- 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
- 信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作。
- 每次对信号量PV操作不仅限于对信号量加1或者减1,可以加减任意正整数。
- POSIX提供两种信号量,命名信号量和无名信号量,命名信号量一般是用于在进程间同步,无名信号量一般用在线程间同步。
五、共享内存(重要)
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区域。(线程之间访问进程属性也属于共享内存,需要同步机制)。
特点
- 共享内存是一种最快的IPC,因为通过API可以直接将物理内存共享区域直接映射到各个进程的虚拟空间地址中,此后进程可以通过虚拟地址访问内存,无需进行任何的用户态和内核态的切换,无需数据拷贝。而管道,消息队列,我们可以看到他们进行通信时都是必须使用固定的API,这就发生了数据拷贝和用户态到内核态的切换,非常耗费性能。
- 因为多个进程可以同步操作,所以需要进行同步。
- 信号量 + 共享内存通常结合在一起使用,信号量来同步对共享内存的访问。
原型
在Linux上提供了POSIX和System V两种共享内存接口,这里介绍的是System V。
流程:通过ftok(const char*pathname.intproj_id)获取key_t唯一标识,然后通过shmget(key_t key,size_t size,int shmflg)来创建共享内存,创建成成功返回共享内存的shmid,失败返回-1,通过void *shmat(int shmid,const void*shmaddr,int shmflg)来使进程可以获取到创建的共享内存的虚拟地址。进程去除关联需要使用shmdt函数,int shmdt(const void *shmaddr)将之前通过shmat附加到进程地址空间的共享内存段移除,调用此函数之后进程将无法通过原地址访问共享内存段。最后通过shmctl来释放共享内存。具体的API不过多赘述,可以通过【Linux】「共享内存揭秘」:高效进程通信的终极指南-云社区-华为云 (huaweicloud.com)查看。
可以使用ipcs 命令来查看当前的共享内存、信号量、消息队列有哪些。使用ipcrm -q id删除消息队列,ipcrm -m id 删除共享内存,ipcrm -s id 删除信号量。
六、信号量
对于进程间的同步API:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>
/*
*/
/*
作用:创建一个新的信号量或取得一个已有的信号量
原型:int semget(key_t key, int nsems, int semflg)
参数:int nsems 它代表信号量集合中有多少个信号量。如果是创建新集合,则必须指定它;如果是引用现有的信号集(通常客户进程中),则将其指定为0.
int semflg 和前面IPC机制类似,用来初始化信号集维护的semid_ds结构中的ipc_perm结构成员sem_perm。通常用IPC_CREAT|0644创建,要直接打开已存在的话 也直接填0就好
*/
/*
用途:该函数用来直接控制信号量信息.也就是直接删除信号量或者初始化信号量.
原型:int semctl(int semid, int semnum, int cmd, ...)
参数:int semid semget函数返回的信号量标识符.
int semnum 它代表要给该信号量集中的第几个信号量设置初值
int cmd 通常用SETVAL/GETVAL设置和获取信号量集中的一个单独的信号量。
如果有第四个参数,取决于所请求的命令,如果使用该参数,它通常是一个union semum结构,定义如下:
union semun
{
int val; Value for SETVAL 通常就要它就够了
struct semid_ds *buf;
unsigned short *arry;
};
*/
/*
用途:用来改变信号量的值,该函数是具有原子性的。
原型:int semop(int semid, struct sembuf *sops, size_t nsops)
参数:sem_id 是由semget函数返回的信号量标识符.
struct sembuf *sops sops是一个指针,指向一个有sembuf结构表示的信号量操作数组,本质上就代表了一个数组地址。
size_t nsops 第三个参数,信号量数组元素个数
sembuf结构如下:
struct sembuf
{
unsigned short int sem_num; 信号量组中的对应的序号,0~sem_nums-1,除非使用一组信号量否则为0
short int sem_op; 信号量值在一次操作中的改变量
short int sem_flg; 操作标志:IPC_NOWAIT(不阻塞) , SEM_UNDO(进程结束时自动释放信号量),填0就好
};
*/
// 信号量操作需要使用如下结构体
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int main() {
// 创建System V信号量
key_t sem_key = ftok("a.out", 'T'); //----获取信号量的键值 用于不同进程间通信
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int sem_id = semget(sem_key, 1, IPC_CREAT | 0666); //----创建信号量
union semun arg;
arg.val = 0;
semctl(sem_id, 0, SETVAL, arg); //----初始值为0
sembuf wait_op = {0, -1, 0}; //----P操作
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
semop(sem_id, &wait_op, 1);
sembuf post_op = {0, 1, 0}; //----V操作
semop(sem_id, &post_op, 1);
semctl(sem_id, 0, IPC_RMID); //----删除信号量
return 0;
}
多线程的同步API:
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <errno.h>
int main()
{
sem_t remain; //创建信号量
sem_init(&remain, 0, 5); //总数初始化为5
sem_wait(&remain); //信号量减1
sem_post(&remain); //信号量加1
return 0;
}
Socket通信
Socket可以实现不同主机的进程之间的通信。知道ip地址和port即可实现客户端和服务器通信。
client.cpp
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
const int PORT = 8080;
const int BUFFER_SIZE = 1024;
int main() {
// 1. 创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
std::cerr << "Socket creation failed" << std::endl;
return 1;
}
// 2. 连接服务器
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); // 本地回环地址
if (connect(sock, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
std::cerr << "Connection failed" << std::endl;
return 1;
}
std::cout << "Connected to server" << std::endl;
// 3. 发送和接收数据
while (true) {
std::cout << "Enter message (type 'exit' to quit): ";
char buffer[BUFFER_SIZE];
std::cin.getline(buffer, BUFFER_SIZE);
if (strcmp(buffer, "exit") == 0) break;
write(sock, buffer, strlen(buffer));
memset(buffer, 0, BUFFER_SIZE);
ssize_t bytes_read = read(sock, buffer, BUFFER_SIZE);
if (bytes_read <= 0) {
std::cerr << "Server disconnected" << std::endl;
break;
}
std::cout << "Server response: " << buffer << std::endl;
}
// 4. 关闭连接
close(sock);
return 0;
}
server.cpp
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
const int PORT = 8080;
const int BUFFER_SIZE = 1024;
int main() {
// 1. 创建套接字
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
std::cerr << "Socket creation failed" << std::endl;
return 1;
}
// 2. 绑定地址和端口
sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡
address.sin_port = htons(PORT); // 端口转网络字节序
if (bind(server_fd, (sockaddr*)&address, sizeof(address)) < 0) {
std::cerr << "Bind failed" << std::endl;
return 1;
}
// 3. 监听连接
if (listen(server_fd, 3) < 0) {
std::cerr << "Listen failed" << std::endl;
return 1;
}
std::cout << "Server listening on port " << PORT << std::endl;
// 4. 接受客户端连接
sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
int client_fd = accept(server_fd, (sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) {
std::cerr << "Accept failed" << std::endl;
return 1;
}
std::cout << "Client connected" << std::endl;
// 5. 接收和发送数据
char buffer[BUFFER_SIZE];
while (true) {
memset(buffer, 0, BUFFER_SIZE);
ssize_t bytes_read = read(client_fd, buffer, BUFFER_SIZE);
if (bytes_read <= 0) {
std::cout << "Client disconnected" << std::endl;
break;
}
std::cout << "Received: " << buffer << std::endl;
// 回显数据
const char* response = "Message received";
write(client_fd, response, strlen(response));
}
// 6. 关闭连接
close(client_fd);
close(server_fd);
return 0;
}
几个demo
通过消息队列和共享内存比较
共享内存代码:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>
#include <chrono>
#include <iostream>
#include <cstring>
struct Data {
long mtype;
long id;
char message[2];
};
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int main() {
const int NUM = 500000;
const int SHM_SIZE = NUM * sizeof(Data);
// 创建System V共享内存
key_t shm_key = ftok("a.out", 'S');
int shm_id = shmget(shm_key, SHM_SIZE, IPC_CREAT | 0666);
if (shm_id == -1) {
perror("shmget failed");
exit(EXIT_FAILURE);
}
Data* shm_data = (Data*)shmat(shm_id, nullptr, 0);
// 创建System V信号量
key_t sem_key = ftok("a.out", 'T');
int sem_id = semget(sem_key, 1, IPC_CREAT | 0666);
union semun arg;
arg.val = 0;
semctl(sem_id, 0, SETVAL, arg); // 初始值为0
pid_t pid = fork();
if (pid == 0) { // 子进程(读取)
auto start = std::chrono::high_resolution_clock::now();
sembuf wait_op = {0, -1, 0}; // P操作
semop(sem_id, &wait_op, 1);
for (int i = 0; i < NUM*100; ++i) {
if (shm_data[i%NUM].id != 0) {
std::cerr << "Data mismatch!" << std::endl;
}
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "SHM Read: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " ms" << std::endl;
shmdt(shm_data);
semctl(sem_id, 0, IPC_RMID);
} else { // 父进程(写入)
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < NUM*100; ++i) {
shm_data[i%NUM].mtype = 1;
shm_data[i%NUM].id = 0;
strncpy(shm_data[i%NUM].message, "H", sizeof(shm_data[i%NUM].message));
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "SHM Write: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " ms" << std::endl;
sembuf post_op = {0, 1, 0}; // V操作
semop(sem_id, &post_op, 1);
wait(nullptr);
shmdt(shm_data);
shmctl(shm_id, IPC_RMID, nullptr);
semctl(sem_id, 0, IPC_RMID);
}
return 0;
}
消息队列代码:
#include <sys/ipc.h>
#include <sys/msg.h>
#include <chrono>
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
struct Message {
long mtype;
long id;
char message[2];
};
int main() {
const int NUM = 50000;
const key_t KEY = ftok("a.out", 'A');
int msgid = msgget(KEY, 0666 | IPC_CREAT);
if (msgid == -1) {
perror("msgget failed");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid == 0) { // 子进程(接收)
auto start = std::chrono::high_resolution_clock::now();
Message msg;
for (int i = 0; i < NUM*100; ++i) {
if (msgrcv(msgid, &msg, sizeof(Message) -sizeof(long), 1, 0) == -1) {
perror("msgrcv");
exit(EXIT_FAILURE);
}
if (msg.id != i) {
std::cerr << "Data mismatch!" << std::endl;
}
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "MQ Read: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " ms" << std::endl;
msgctl(msgid, IPC_RMID, nullptr);
} else {
// 父进程(发送)
Message msg[NUM] ;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < NUM*100; ++i) {
msg[i%NUM].id = i;
msg[i%NUM].mtype = 1;
strncpy(msg[i%NUM].message, "H", sizeof(msg[i%NUM].message));
if (msgsnd(msgid, &msg[i%NUM], sizeof(Message) -sizeof(long), 0) == -1) {
perror("msgsnd");
exit(EXIT_FAILURE);
}
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "MQ Write: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " ms" << std::endl;
wait(nullptr); // 等待子进程退出
}
return 0;
}
测试:进行5000000次的数据数据操作
共享内存:
消息队列:
减少数据量:进行50000次数据操作
共享内存:
消息队列:
可见共享内存的速度是真的快,无需进行用户态到内核态切换,速度可达消息队列的5~10倍。
管道 + 共享内存实现通信
使用管道来触发事件,共享内存传递数据。
comm.hpp
#pragma once
#include<iostream>
#include<cstdlib>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <sys/ipc.h>
#include <string.h>
#include <sys/shm.h>
#include <unistd.h>
const std::string pathname="a.out";
const int proj_id=0x11223346;
const int size=4096;
const std::string filename="fifo";
key_t GetKey()
{
key_t key=ftok(pathname.c_str(),proj_id);
if(key<0)
{
std::cerr<<"errno: "<<errno<<", errstring: "<<strerror(errno)<<__FILE__<<":"<<__LINE__<<std::endl;;
exit(1);
}
return key;
}
std::string ToHex(int id)
{
char buffer[1024];
snprintf(buffer,sizeof(buffer),"0x%x",id);
return buffer;
}
int CreateShmHelper(key_t key,int flag)
{
int shmid=shmget(key,size,flag);
if(shmid<0)
{
std::cerr<<"errno: "<<", errstring: "<<strerror(errno)<<__FILE__<<":"<<__LINE__<<std::endl;
exit(2);
}
return shmid;
}
int CreateShm(key_t key)
{
return CreateShmHelper(key, IPC_CREAT | IPC_EXCL | 0644);
}
// 获取共享权限的shmid
int GetShm(key_t key)
{
return CreateShmHelper(key, IPC_CREAT);
}
bool MakeFifo()
{
int n=mkfifo(filename.c_str(),0666);
if(n<0)
{
std::cerr<<"errno: "<<errno<<", errstring: "<<strerror(errno)<<__FILE__<<":"<<__LINE__<<std::endl;;
return false;
}
std::cout<<"mkfifo success... read"<<__FILE__<<":"<<__LINE__<<std::endl;;
return true;
}
client.cc
#include "comm.hpp"
int main()
{
key_t key=GetKey();
int shmid= CreateShm(key);
char*s=(char*)shmat(shmid,nullptr,0);
std::cout<<"attach shm done"<<std::endl;
int fd=open(filename.c_str(),O_WRONLY);
sleep(5);
for(char c='a';c<='z';c++)
{
s[c-'a']=c;
std::cout<<"write: "<<c<<" done"<<std::endl;
sleep(1);
int code=1;
write(fd,&code,sizeof(4));
}
shmdt(s);
std::cout<<"detach shm done"<<std::endl;
close(fd);
return 0;
}
server.cc
#include "comm.hpp"
class Init
{
public:
Init()
{
bool r=MakeFifo();
if(!r) return;
key_t key=GetKey();
std::cout<<"key: "<<ToHex(key)<<std::endl;
sleep(3);
shmid=GetShm(key);
std::cout<<"开始将shm映射到进程的地址空间中"<<std::endl;
s=(char*)shmat(shmid,nullptr,0);
fd=open(filename.c_str(),O_RDONLY);
}
~Init()
{
sleep(5);
shmdt(s);
std::cout<<"开始将shm从进程地址空间中移除"<<std::endl;
sleep(5);
shmctl(shmid,IPC_RMID,nullptr);
std::cout<<"开始将shm从OS中删除"<<std::endl;
close(fd);
}
public:
int shmid;
int fd;
char*s;
};
int main()
{
Init init;
sleep(5);
while(true)
{
int code=0;
ssize_t n=read(init.fd,&code,sizeof(code));
if(n>0)
{
std::cout<<"共享内存的内容:"<<init.s<<std::endl;
sleep(1);
}
else if(n==0)
{
break;
}
}
sleep(10);
return 0;
}
共享内存 + 信号量 + 消息队列
实现服务器进程和客户端进程通信,共享内存传递数据,信号量同步,消息队列通知服务器读取。
#include<stdio.h>
#include<stdlib.h>
#include<sys/shm.h> // shared memory
#include<sys/sem.h> // semaphore
#include<sys/msg.h> // message queue
#include<string.h> // memcpy
// 消息队列结构
struct msg_form {
long mtype;
char mtext;
};
// 联合体,用于semctl初始化
union semun
{
int val; /*for SETVAL*/
struct semid_ds *buf;
unsigned short *array;
};
// 初始化信号量
int init_sem(int sem_id, int value)
{
union semun tmp;
tmp.val = value;
if(semctl(sem_id, 0, SETVAL, tmp) == -1)
{
perror("Init Semaphore Error");
return -1;
}
return 0;
}
// P操作:
// 若信号量值为1,获取资源并将信号量值-1
// 若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/
sbuf.sem_op = -1; /*P操作*/
sbuf.sem_flg = SEM_UNDO;
if(semop(sem_id, &sbuf, 1) == -1)
{
perror("P operation Error");
return -1;
}
return 0;
}
// V操作:
// 释放资源并将信号量值+1
// 如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/
sbuf.sem_op = 1; /*V操作*/
sbuf.sem_flg = SEM_UNDO;
if(semop(sem_id, &sbuf, 1) == -1)
{
perror("V operation Error");
return -1;
}
return 0;
}
// 删除信号量集
int del_sem(int sem_id)
{
union semun tmp;
if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
{
perror("Delete Semaphore Error");
return -1;
}
return 0;
}
// 创建一个信号量集
int creat_sem(key_t key)
{
int sem_id;
if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
{
perror("semget error");
exit(-1);
}
init_sem(sem_id, 1); /*初值设为1资源未占用*/
return sem_id;
}
int main()
{
key_t key;
int shmid, semid, msqid;
char *shm;
char data[] = "this is server";
struct shmid_ds buf1; /*用于删除共享内存*/
struct msqid_ds buf2; /*用于删除消息队列*/
struct msg_form msg; /*消息队列用于通知对方更新了共享内存*/
// 获取key值
if((key = ftok(".", 'z')) < 0)
{
perror("ftok error");
exit(1);
}
// 创建共享内存
if((shmid = shmget(key, 1024, IPC_CREAT|0666)) == -1)
{
perror("Create Shared Memory Error");
exit(1);
}
// 连接共享内存
shm = (char*)shmat(shmid, 0, 0);
if((int)shm == -1)
{
perror("Attach Shared Memory Error");
exit(1);
}
// 创建消息队列
if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
{
perror("msgget error");
exit(1);
}
// 创建信号量
semid = creat_sem(key);
// 读数据
while(1)
{
msgrcv(msqid, &msg, 1, 888, 0); /*读取类型为888的消息*/
if(msg.mtext == 'q') /*quit - 跳出循环*/
break;
if(msg.mtext == 'r') /*read - 读共享内存*/
{
sem_p(semid);
printf("%s\n",shm);
sem_v(semid);
}
}
// 断开连接
shmdt(shm);
/*删除共享内存、消息队列、信号量*/
shmctl(shmid, IPC_RMID, &buf1);
msgctl(msqid, IPC_RMID, &buf2);
del_sem(semid);
return 0;
}
#include<stdio.h>
#include<stdlib.h>
#include<sys/shm.h> // shared memory
#include<sys/sem.h> // semaphore
#include<sys/msg.h> // message queue
#include<string.h> // memcpy
// 消息队列结构
struct msg_form {
long mtype;
char mtext;
};
// 联合体,用于semctl初始化
union semun
{
int val; /*for SETVAL*/
struct semid_ds *buf;
unsigned short *array;
};
// P操作:
// 若信号量值为1,获取资源并将信号量值-1
// 若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/
sbuf.sem_op = -1; /*P操作*/
sbuf.sem_flg = SEM_UNDO;
if(semop(sem_id, &sbuf, 1) == -1)
{
perror("P operation Error");
return -1;
}
return 0;
}
// V操作:
// 释放资源并将信号量值+1
// 如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/
sbuf.sem_op = 1; /*V操作*/
sbuf.sem_flg = SEM_UNDO;
if(semop(sem_id, &sbuf, 1) == -1)
{
perror("V operation Error");
return -1;
}
return 0;
}
int main()
{
key_t key;
int shmid, semid, msqid;
char *shm;
struct msg_form msg;
int flag = 1; /*while循环条件*/
// 获取key值
if((key = ftok(".", 'z')) < 0)
{
perror("ftok error");
exit(1);
}
// 获取共享内存
if((shmid = shmget(key, 1024, 0)) == -1)
{
perror("shmget error");
exit(1);
}
// 连接共享内存
shm = (char*)shmat(shmid, 0, 0);
if((int)shm == -1)
{
perror("Attach Shared Memory Error");
exit(1);
}
// 创建消息队列
if ((msqid = msgget(key, 0)) == -1)
{
perror("msgget error");
exit(1);
}
// 获取信号量
if((semid = semget(key, 0, 0)) == -1)
{
perror("semget error");
exit(1);
}
// 写数据
printf("***************************************\n");
printf("* IPC *\n");
printf("* Input r to send data to server. *\n");
printf("* Input q to quit. *\n");
printf("***************************************\n");
while(flag)
{
char c;
printf("Please input command: ");
scanf("%c", &c);
switch(c)
{
case 'r':
printf("Data to send: ");
sem_p(semid); /*访问资源*/
scanf("%s", shm);
sem_v(semid); /*释放资源*/
/*清空标准输入缓冲区*/
while((c=getchar())!='\n' && c!=EOF);
msg.mtype = 888;
msg.mtext = 'r'; /*发送消息通知服务器读数据*/
msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
break;
case 'q':
msg.mtype = 888;
msg.mtext = 'q';
msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
flag = 0;
break;
default:
printf("Wrong input!\n");
/*清空标准输入缓冲区*/
while((c=getchar())!='\n' && c!=EOF);
}
}
// 断开连接
shmdt(shm);
return 0;
}
mmap
内存映射是将磁盘文件的数据映射到内存,用户可以通过修改内存就修改磁盘文件,这样无需每次进行从用户态到内核态的陷入,因此对于随机访问且大文件数据场景下效率很高。
当使用mmap映射文件到内存时,操作系统使用页缓存来优化对文件数据的访问,页缓存是操作系统一部分,用户存储磁盘读取到的数据页。访问mmap映射的文件时,并不是每次读取都会直接访问磁盘,如果所需数据存在页缓存,则直接从缓存读取数据,无需磁盘I/O。
mmap利用到了延迟加载,意味着文件数据只有在实际被访问时才加载到内存,这反映了一种节约和高效的资源管理策略。还用到了写时复制COW技术,当多个进程映射同一个文件时,他们共享相同的物理内存页,一旦某个进程要修改当前页面,并且是MAP_PRIVATE映射时,就会为该页创建独立的副本,保证其他进程视图不变。
内存映射分为两种:文件映射和匿名映射
文件映射
将文件一部分映射到调用进程虚拟内存中,对文件访问转化为内存区域的字操作,页面按需从文件中加载和写入。
#include<sys/mman.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
using namespace std;
char *p=NULL;
int fd =0;
void hadler(int)
{
munmap(p, 1024);
close(fd);
printf("munmap and close\n");
exit(0);
}
/*
void* mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr:映射的起始地址,通常为NULL
length:映射的长度
prot:映射区域的保护方式
PROT_READ:可读
PROT_WRITE:可写
PROT_EXEC:可执行
flags:映射的类型
MAP_SHARED:共享映射,多个进程可以共享同一块内存
MAP_PRIVATE:私有映射,修改不会影响其他进程.并且将无法写入到文件中
MAP_ANONYMOUS:匿名映射,映射到内存中而不是文件,此时可以忽略fd(设为-1),没有血缘关系进程不能使用
fd:文件描述符,匿名设为-1
offset:文件偏移量,映射的起始位置 offset必须是页大小的整数倍
返回值:成功返回映射的地址,失败返回MAP_FAILED
*/
/*
int munmap(void *addr, size_t length);
addr:指向要解除映射的内存起始地址
length:解除映射的长度
*/
/*
当共享映射时可以使用msync手动将内存数据写入文件,也可以由操作系统自动写入文件
int msync(void *addr, size_t length, int flags);
addr:指向要同步的内存起始地址
length:同步的长度
flags:同步的方式
MS_ASYNC:异步同步
MS_SYNC:同步同步
MS_INVALIDATE:无效化映射
返回值:成功返回0,失败返回-1
*/
int main()
{
fd = open("test.txt",O_RDWR,0644);
if (fd == -1) {
perror("open");
exit(-1);
}
p = (char *)mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
signal(SIGINT,hadler);
if(p == MAP_FAILED)
{
perror("mmap");
exit(-1);
}
int i =0;
while(1)
{
sprintf(p,"------------%d-------------\n",i++);
sleep(1);
}
return 0;
}
匿名映射
匿名映射仅仅用于有血缘关系的进程之间的通信,没有对应文件,映射页面被初始化为0.
#include<sys/mman.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<string.h>
#include<sys/wait.h>
/*
void* mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr:映射的起始地址,通常为NULL
length:映射的长度
prot:映射区域的保护方式
PROT_READ:可读
PROT_WRITE:可写
PROT_EXEC:可执行
flags:映射的类型
MAP_SHARED:共享映射,多个进程可以共享同一块内存
MAP_PRIVATE:私有映射,修改不会影响其他进程.并且将无法写入到文件中
MAP_ANONYMOUS:匿名映射,映射到内存中而不是文件,此时可以忽略fd(设为-1),没有血缘关系进程不能使用
fd:文件描述符,匿名设为-1
offset:文件偏移量,映射的起始位置 offset必须是页大小的整数倍
返回值:成功返回映射的地址,失败返回MAP_FAILED
*/
/*
int munmap(void *addr, size_t length);
addr:指向要解除映射的内存起始地址
length:解除映射的长度
*/
/*
当共享映射时可以使用msync手动将内存数据写入文件,也可以由操作系统自动写入文件
int msync(void *addr, size_t length, int flags);
addr:指向要同步的内存起始地址
length:同步的长度
flags:同步的方式
MS_ASYNC:异步同步
MS_SYNC:同步同步
MS_INVALIDATE:无效化映射
返回值:成功返回0,失败返回-1
*/
int main()
{
char* p = (char *)mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
if(p == MAP_FAILED)
{
perror("mmap");
exit(-1);
}
int pid = fork();
if(pid <0)
{
munmap(p, 1024);
perror("fork");
exit(-1);
}
if(pid>0)
{
strcpy(p, "hello world");
wait(nullptr);
}
else
{
sleep(1);
printf("child: %s\n", p);
}
if(munmap(p, 1024)==-1)
{
perror("munmap");
exit(-1);
};
return 0;
}