文章目录
- 共享内存(Share Memory)
- 信号队列(Message Queue)
- 信号量(semaphore)
进程间通信的核心理念:让不同的进程看见同一块资源
linux下的通信方案: SYSTEM V
共享内存(Share Memory)
特点:1.共享内存是进程见通信最最快的
2.可以提供较大通信空间
注意:共享内存由于裸露给所有使用者,因此是需要维护的
做法:去申请一块空间,让其映射到对应的不同进程的进程地址空间。
如图:
那么具体是怎么做的呢?
- linux是生成一个特定的key,有key作为表示这块共享内存的唯一标识。
- 不同进程在运行的时候凭借这个key拿到共享内存的shmid(类似于文件管理系统的fd),进行挂接到自己的进程地址空间上。
- 申请的空间是不会自己释放的,要么在程序里面用funtion控制,要么在外部手动释放
-
ipcs -m
-
#查看当前有哪些共享内存
-
ipcrm -m shmid
-
#删除对应的共享内存id
需要用到的系统调用:
shmget #创建共享内存
shmat # 挂接共享内存
shmdt # 取消挂接
shmctl # 操控这块共享内存
unlink # 删除文件
server:
#include "Common.hpp"class Init
{
public:Init(){bool r = MakeFifo();//用管道是为了进程进行时,具备一定顺序性。if (!r)return;key_t key = GetKey();shmid = CreatShm(key);std::cout << "shmid:" << shmid << "\n";// sleep(5);std::cout << "开始将shm映射到进程地址空间\n";s = (char *)shmat(shmid, nullptr, 0);fd = open(filename.c_str(), O_RDONLY);}~Init(){close(fd);std::cout << "将shm从进程地址空间移除\n";shmdt(s);std::cout << "将共享内存从操作系统中释放\n";shmctl(shmid, IPC_RMID, nullptr);unlink(filename.c_str());}int FileDirection(){return fd;}const char *ShnPtr(){return s;}private:int shmid;int fd;char *s;
};int main()
{Init init;// struct shmid_ds ds;// std::cout<<std::hex<<ds.shm_perm.__key<<"\n";// std::cout<<ds.shm_nattch<<"\n";while (true){int code = 0;ssize_t n = read(init.FileDirection(), &code, sizeof(code));if (n > 0){std::cout << "共享读取:" << init.ShnPtr() << "\n";}else if (n == 0){break;}else{std::cerr << "读取错误,错误码:" << errno << "\n";}}return 0;
}
client
#include "Common.hpp"int main()
{key_t key = GetKey();int shmid = CreatShmHelper(key, IPC_CREAT | 0644);char *s = static_cast<char *>(shmat(shmid, nullptr, 0));std::cout << "attach shm done\n";int fd = open(filename.c_str(), O_WRONLY);for (int c = 0; c < 26; c++){s[c] = c + 'a';std::cout << "write:" << (char)c + 'a' << "done\n";sleep(1);int code = 1;write(fd, (char *)&code, sizeof(code));}// sleep(5);std::cout << "dettach shm done\n";shmdt(s);close(fd);return 0;
}
.h
#pragma once#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <fcntl.h>const std::string pathname = "/home/fuh_cs/Desktop/cpp_learning/linux/ShareMemory/Common.hpp";
const int proj_id = 0x234;
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 << ",errnostring:" << std::strerror(errno) << std::endl;exit(-1);}return key;
}int CreatShmHelper(key_t key, int flag)
{int shmid = shmget(key, size, flag); //共享内存也有权限也是需要设置的// EXCL保证创建时如果存在就会失败if (shmid < 0){std::cerr << "errno:" << errno << ",errnostring:" << std::strerror(errno) << std::endl;exit(2);}return shmid;
}int CreatShm(key_t key)
{return shmget(key, size, IPC_CREAT | IPC_EXCL | 0644); //共享内存也有权限也是需要设置的
}int GetShm(key_t key)
{return shmget(key, size, IPC_CREAT); //共享内存也有权限也是需要设置的
}//为1创建成功
bool MakeFifo()
{int n = mkfifo(filename.c_str(), 0666);if (n < 0){std::cerr << "errno:" << errno << ",errstring" << strerror(errno) << std::endl;return 0;}std::cout << "mkfifo success..." << std::endl;return 1;
}
信号队列(Message Queue)
基于SYSTEM V的还有对应的信号队列(Message Queue),信号量
下面展示下Message Queue的简单使用代码。
基本的系统调用函数,在下面代码中有,具体使用可以通过man手册查询。
#pragma once#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <fcntl.h>
#include <sys/msg.h>
key_t GetKey()
{key_t key = ftok(pathname.c_str(), proj_id);if (key < 0){std::cerr << "errno:" << errno << ",errnostring:" << std::strerror(errno) << std::endl;exit(-1);}return key;
}//消息队列也是存在于内核之中,不手动关闭的生命周期和内核一起int main()
{key_t key = GetKey();int msgid = msgget(key,IPC_CREAT |IPC_EXCL);std::cout<<"msgid:"<<msgid<<'\n';struct msqid_ds ds;std::cout<<ds.__msg_cbytes<<'\n';std::cout<<ds.msg_perm.__key<<'\n';//用msgsend来发送消息//用msgrcv来接受消息msgctl(msgid,IPC_RMID,nullptr);//也可以 ipcrm -q msgid 在bash删除return 0;}
信号量(semaphore)
前置知识:
- 公共资源:多个执行流看见的同一份资源
- 多个执行流访问同一份资源,就存在并发访问
- 为了解决并发访问公共资源的问题,导致的数据不一致、脏读等问题,需要保护资源
- 因此引发出互斥和同步
- 互斥:同一时刻,只能有一个执行流访问资源,加锁完成
- 同步:多个执行流按照预定的先后次序来访问公共资源
- 被保护起来的资源,称为临界资源
- 访问临界资源的执行流(或者代码),称为临界区
分析: - 本质是个计数器
- 当进程需要访问公共资源,先获取信号量,再去访问资源。(相当于信号量是获取资源的凭证,有了信号量,就一定会有资源)没获取信号量的进程,就阻塞等待。
- 信号量如果被获取了,就没了,没被获取,就全部都在。是只有两种状态,二元性的。因此由此二元性,完成了互斥的功能
- 不同的进程也需要看到同一份信号量,因此信号量也被纳入IPC体系,也就是说,信号量由操作系统提供
- 我们知道SYSTEM V的资源是可以看见的,但是信号量是原子的(atomic,不可在分的),即使被进程竞争的访问,也只会出现要么获取了,要么没获取信号量。不会出现获取半个的情况。这种原子的获取操作称之为P操作。相对应原子的释放,称之为V操作。
信号量系统调用
semget
semctl
semop
linux内核看SYSTEM V设计的共享内存等通信方式
一般来说:
- 内核里面有一个ipc_id_ary,其是一个柔性数组,存储了一个size表示大小和指针数组,size表示指针数组的个数
- 这个指针数组所存的指针类型是 **kern_ipc_perm***的指针类型
- 由SYSTEM V标准下设计出来的共享内存,信号队列等,内核里面都是由一个自己类型的结构体去管理的(例如:msg_queue,就是管理信号队列的结构体,Shmid_Kernel, 就是管理共享内存的结构体)
- 而这些结构体的第一个元素,都被设计成相似的结构体(信号队列是:q_perm,共享内存是:shm_perm),这些结构体所包含的元素类型,其实和 kern_ipc_perm的结构体元素类型是一样的。
- 这就使得我们存储kern_ipc_perm的指针,即使存了msg_queue的结构体指针,只需要通过强制类型转换,也是可以访问msg_queue结构体
- 这样对SYSYTEM V设计下的共享内存,信号队列等可以统一管理
上面这种内核的设计:实际就是利用了C语言的特点,实现了多态,有种通过父类指针访问子类的类似