文章目录
- 共享内存(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语言的特点,实现了多态,有种通过父类指针访问子类的类似