开发软件下载网站,网页图片居中怎么设置,微信小程序打不开怎解决,网站接入目录
前言
1. System V IPC
2. 共享内存
系统调用接口
shmget ftok shmat shmdt
shmctl
共享内存的读写
共享内存的描述对象
3. 消息队列
msgget
msgsnd msgctl 消息队列描述对象
4. 信号量 系统调用接口 semget
semctl
信号量描述对象
5. 系统层面IPC资源
6.…
目录
前言
1. System V IPC
2. 共享内存
系统调用接口
shmget ftok shmat shmdt
shmctl
共享内存的读写
共享内存的描述对象
3. 消息队列
msgget
msgsnd msgctl 消息队列描述对象
4. 信号量 系统调用接口 semget
semctl
信号量描述对象
5. 系统层面IPC资源
6. 补充
总结 前言 前边学习了管道通信本文再来聊一聊其他的进程间通信方式共享内存、消息队列、信号量 1. System V IPC System V IPCInter-Process Communication是 Unix 操作系统的一种进程间通信机制。它提供了一组系统调用允许不同进程之间共享数据和信息主要有三种通信方式共享内存、消息队列、信号量通常用于需要高效数据交换和同步的应用场景
2. 共享内存 · 前边提到的的两种管道都可以实现进程之间单向通信但对于一些数据量大且需要频繁传输数据时效率就显得有点低为了满足需求并统一标准于是便出现共享内存这一通信标准 共享内存作为一种进程间通信的标准主要是为了解决管道通信在大数据量和频繁传输数据时效率较低的问题。共享内存允许多个进程共享同一块内存区域进程可以直接访问内存中的数据避免了数据的复制和传输开销从而提高了通信的效率 共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间这些进程间数据传递不再涉及到内核换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据 进程间通信的前提让不同的进程看到同一份资源由操作系统提供 操作系统只允许两个进程使用共享内存通信吗? 肯定不是操作系统允许系统中同时存在多个共享内存对于创建的多个共享内存当然也需要进行管理先描述再组织存在一个struct shm的结构体;用于对共享内存的描述;(共享内存对象)每个新建的共享内存都有一个结构体描述对象这样对多个共享内存的管理就转换成了对结构体对象的管理
构建信道:
创建共享内存区域。将共享内存映射到进程地址空间进行通信
关闭信道:
解除共享内存映射减少引用计数。释放共享内存
问题来了如何保证第二个参与通信的进程找到的就是同一个共享内存呢匿名管道可以通过父子进程继承的方式找到命名管道可以通过文件系统找到共享内存如何保证信道成功建立呢?存在一个唯一的标识进行识别
系统调用接口
shmget
shmget 用于创建一个新的共享内存段或者获取一个已经存在的共享内存段的标识符 shmflg标志参数目前只需了解两个
IPC_CREAT:共享内存不存在就创建(创建后把标识符写到共享内存属性中)存在就获取与返回共享内存标识符IPC_EXCL:不单独使用一般与IPC_CREAT一起使用(IPC_EXCL | IPC CREAT):如果不存在就创建如果存在就出错返回;主要用于确保创键的是一个新的共享内存; 补充
共享内存(IPC资源)的生命周期是随内核的ipcrm -m 删除共享内存ipcs -m查看存在的共享内存shmid共享内存id使用这个共享内存时我们一般使用shmid来进行操作共享内存Key:一般不在应用层使用只用来标识shm的唯一性; ftok 前边我们提到如何让第二个进程找到共享内存答案就是标识符这里的key就是标识符通信双方约定一个标识符一方将标识符写入到共享内存对象中另一方可以遍历进行查找标识符就可以保证它们找到的是同一份资源 这里的标识符理论上是可以随便写一个只不过随便写的方式很容易与系统中已经存在的标识符冲突所以一般会使用算法来生成唯一的标识符;这个接口就是ftok ftok()函数会使用指定的路径名和项目标识符来计算一个key值该key值在系统中是唯一的返回的key值可以作为参数传递给shmget()函数从而指定共享内存段的标识符 pathname参数是一个指向以null结尾的路径名的指针。它的作用是为了生成一个唯一的key值。ftok()函数会使用指定的路径名来获取文件的inode号然后将其与proj id参数合并生成一个唯一的key值; 这个路径名并不需要对应一个真实存在的文件它只是用作生成key值的一个标识。通常情况下会选择一个在系统中唯一的、不会轻易更改的路径名来确保生成的key值是唯一的以避免不同的进程之间出现key冲突; pathname参数要指向一个可靠的文件路径比如一个特定的系统文件或者应用程序中的固定路径。这样可以确保在不同的系统中或者不同的程序中使用相同的路径名生成的key值是相同的 shmat
shmat() 用于将共享内存段连接到调用进程的地址空间形成一个虚拟地址的映射 接口返回值void*返回共享内存在共享区映射的起始地址 shmdt shmdt 用于将共享内存段从进程的地址空间中分离即解除该共享内存段与进程地址空间的映射关系 成功shmdt()函数返回0如果失败返回-1并设置errno来指示错误类型
shmctl shmctl控制共享内存段的函数包含获取共享内存段信息、设置共享内存段的权限、删除共享内存段等它的功能很多当前我们主要使用删除的功能
IPC RMID:删除共享内存段 为什么我们使用标识符key需要使用者自己生成为什么OS不直接自己生成? 就是为了让使用者知道然后好让目标进程通过这个值找到共享内存如果让系统生成使用者不知道标识符那目标进程又如何知道这个标识符呢?内存中有多个共享内存目标进程又如何去找自己的共享内存呢?根本不可能要想找到共享内存就必须两进程提前约定好标识符这样才能找到共享内存
共享内存的读写 共享内存在用户空间如何控制读写呢 在共享内存的开头或者结尾留了一块区域用来存储共享内存的属性比如读位置、写位置
删除数据呢和vector删除一样不需要清空数据只需要数据变无效即可
特点
共享内存通信不提供同步机制它是直接裸露给使用者的所以一定要注意共享内存的使用安全问题;共享内存是所有进程通信速度最快的;共享内存可以提供较大的空间; 为什么说共享内存是最快的通信方式
对比一下管道 进程A要想把数据通过管道发送给进程B先从用户空间拷贝到管道内核空间进程B通过read把数据从管道再拷贝一份到自己的用户空间
这里是很影响效率的具体分析一下先把数据写到自定义的缓冲区然后再把缓冲区的数据拷贝到内核空间的管道读方再把数据从内核空间拷贝到自己定义的缓冲区 共享内存 进程A只要写入数据就直接写入到了共享内存中而进程B立马就能看到共享内存的写入原理和malloc申请的空间使用类似都是直接写入到内存中不需要什么缓冲区更不需要来回进行拷贝共享内存不需要内核级别的拷贝所以相对于管道来说效率更高在应用场景中减少了拷贝的次数
共享内存的描述对象 以上是部分属性 编写一个简单的程序使用并验证一下 comm.hpp
#pragma once#include iostream
#include sys/types.h
#include sys/ipc.h
#include sys/shm.h
#include cstring
#include fcntl.h
#include sys/types.h
#include sys/stat.hconst std::string pathname /home/test/test/share_memory;
const int proj_id 0x11223344;
//共享内存的大小建议设置成4096的整数倍
const int size 4096;
key_t Getkey()
{key_t k ftok(pathname.c_str(), proj_id);//把字符串转化成C字符串风格if (k 0){std::cerr error: errno errorstring: strerror(errno) std::endl;exit(1);}//std::cout key: k std::endl;return k;
}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 error: errno errorstring: strerror(errno) std::endl;exit(2);}return shmid;
}int CreateShm(key_t key)
{return CreateShmHelper(key, IPC_CREAT | IPC_EXCL | 0664);
}int GetShm(key_t key)
{return CreateShmHelper(key, IPC_CREAT);
} test.cc
class Init
{
public:Init(){key_t key Getkey();std::cout key: ToHex(key) std::endl;shmid CreateShm(key);// sleep(5);std::cout shmid: shmid std::endl;std::cout 开始将shm映射到进程的地址空间中 std::endl;// sleep(5);s (char*)shmat(shmid, nullptr, 0);// 打开管道用于发送信息进行控制如果管道写端没有连接就一直阻塞// fd open(filename.c_str(), O_RDONLY);}~Init(){shmdt(s);std::cout 开始将shm从进程的地址空间中移除 std::endl;// sleep(5);shmctl(shmid, IPC_RMID, nullptr);std::cout 开始将shm从OS中删除 std::endl;// sleep(10);// close(fd);// unlink(filename.c_str());}public:int shmid;int fd;char* s;
};int main(){Init init;//获取共享内存属性struct shmid_ds ds;shmctl(init.shmid,IPC_STAT, ds);std::cout ToHex(ds.shm_perm.__key) std::endl;std::cout ds.shm_segsz std::endl;std::cout ds.shm_atime std::endl;std::cout ds.shm_nattch std::endl;
}使用命令查看 这也就验证了之前所提到的共享内存对象共享内存 共享内存空间 共享内存属性
3. 消息队列 消息队列:提供一个进程给另一个进程发送数据块的能力
结构 消息队列是OS中维护的一个数据结构当一个进程想要把数据发送给另一个进程就可以把数据块连接在消息队列上成为消息队列的一个节点
msgget
msgget接口可以用于获取已经存在的消息队列的标识符还可以用于创建新的消息队列; key需要在用户层进行设置并且这个key可以被A进程看到也可以被B进程看到还要写到消息队列的属性中和shmget中的类似
msgflg也是一样的示例
int msgid msgget(key, IPC_CREAT | IPC_EXCL);
这个队列中不仅A进程可以加入数据块B进程也可以;这时就产生了新的问题:它们如何知道哪个是自己想要的节点? 为了分辨出哪个是节点是进程需要的所以队列中的数据块需要有类型
msgsnd 发送消息 消息节点 使用示例
struct msgbuf { long mtype; // 消息类型 char mtext[256]; // 消息内容
};int msgid msgget(key, IPC_CREAT | 0666);// 填充内容
struct msgbuf message;
message.mtype 1; // 设置消息类别
snprintf(message.mtext, sizeof(message.mtext), Hello, World!);
msgsnd(msgid, message, strlen(message.mtext) 1, 0) msgctl
这里依然和shmctl的接口很类似当前主要使用它的删除功能(删除消息队列) 使用示例
msgctl(msgid, IPC_RMID, nullptr); // 删除消息队列
msgctl(msgid, IPC_STAT, ds); // 获取消息队列状态 消息队列的属性和接口根共享内存都是十分相似;在系统中也是允许同时有多个消息队列存在的所以在内核中也需要把消息队列管理起来;先描述再组织消息队列 队列 队列属性 消息队列描述对象 也有一个 struct ipc_perm类型的对象
消息队列中的struct_ipc_perm 和共享内存中的struct ipc_perm两个其实是完全一样的用于记录该对象的权限、拥有者、访问权限等信息
4. 信号量 如何理解信号量信号量本质其实就是一个计数器作用用来保护共享资源 举个例子 比如看电影我们去电影院看电影去看之前需要先买票是买到票后指定的座位属于你还是坐到座位上后座位才属于你(排除没素质的情况)当然是买完票以后那个座位就属于你了 电影院和座位就是多人共享公共资源买票的本质就是对资源的预定电影院中有固定的座位那么在购票系统中就会存在一个计数器用于记录剩下的座位数如果买票成功计数器就减减而这里的座位和电影院就是OS公共资源每个人就是进程而购票系统的计数器就是信号量规定好信号量初始值就可以控制访问资源的进程数量
信号量表示资源数目的计数器每个执行流(进程)想要访问公共资源内的某一份资源不能让执行流直接访问而是应该先申请信号量资源本质就是对信号量计数器做--操作。只要--成功就完成了对资源的预定机制 如果申请不成功执行流就会被挂起 访问公共资源的代码执行前先申请信号量如果申请成功才让访问申请失败就挂起阻塞;这样就形成了保护机制 当信号量的数值设为1int sem 1这时就只允许一个执行流读取公共资源执行流在读取时其他执行流只能阻塞等待 sem 就只有两种状态:1、0二元信号量---互斥锁---完成互斥功能 信号量存在的本质就是为了保护公共资源信号量是公共资源那谁来保护信号量信号量都没法保护那又怎么保护其他公共资源? 但好在信号量公共资源内容较为简单申请时只需要--操作--操作只要符合原子性就可以也就是说要么申请完成要么不申请不存在其他情况这样就避免了出现其他情况
申请信号量的操作 —P
释放信号量的操作—V
细节分析:
信号量是公共资源的计数器那么就需要先让所有进程都能看到信号量资源 —— 只能由操作系统提供——纳入到IPC体系信号量本质也是公共资源 对信号量管理的理解
// 单个信号量
{struct sem;int count;struct task struct *wait queue;
} 系统调用接口 semget nsems表示要创建的几个信号量其余两个参数和shmget一样的
示例
int semid semget(key, n, IPC_CREAT | 0666); // 创建一个信号量 0666 是读写权限
// n表示创建信号量的个数 system V 允许创建一批(至少一个)信号量如何支持创建一批在创建时设置n创建个数
semctl
创建了信号量如何设置信号量的计数使用 semctl 函数来实现 示例
// 设置信号量的值为初始值
// 初始化计数为1
// 0 表示您要设置第一个信号量的值
semctl(semid, 0, SETVAL, 1); 设置一组信号量呢 int initial_values[] {1, 1, 1}; // 假设每个信号量的初始值为 1 for (int i 0; i num_semaphores; i) { semctl(semid, i, SETVAL, initial_values[i]) {} 当然也可以删除信号量
semctl(semid, 0, IPC_RMID)
信号量描述对象 信号量允许存在多个那么就需要对其进行管理那么自然也有信号量描述对象发现他也有struct ipc_perm对象
5. 系统层面IPC资源
站在OS角度内核是如何看待ICP资源的它是OS内单独设计的模块 共享内存、消息队列、信号量它们在自己的描述对象开始都有一个ipc_perm 结构体通过对这个结构体的管理就可以达到对这三种IPC资源的管理 回顾一下柔性数组 kern_ipc_perm跟共享内存、消息队列以及信号量中的ipc_perm的内容一致可以把它们看待成同一个结构体 统一使用该数组实现对IPC资源的管理遍历这个数组就可以通过 key 来判断某个IPC资源是否存在 数组中存放的是各种IPC资源的struct ipc_perm 结构体对象地址每当需要增加一个IPC对象(共享内存、消息队列、信号量)时就只需在数组中新增一个struct ipc_perm对象地址即可
比如想要访问共享内存对象中除struct ipc_perm 外的其他资源怎么办直接使用*p肯定不行只需要强转一下类型就可以比如p[1]存放的是消息队列的ipc_perm
((msg_queue*)p[1])-...
把struct ipc perm*类型的指针强转成消息队列对象类型的指针这样就可以访问了; 这个设计模式很类似于多态通过对相同类型的对象访问就可以实现对不同IPC资源的管理
想要更深入的了解可以拜读参考这篇文章进程间通信共享内存和信号量的统一封装机制原理与实现
6. 补充
进程间通信流程
进程间通信 --多个执行流看到同一份资源(公共资源) -- 并发访问 -- 数据干扰问题 -- 保护数据 -- 同步和互斥 互斥任何时刻只允许一个执行流(进程)访问公共资源比如:管道只允许一个读一个写互斥可以通过加锁完成同步多个执行流执行时按照一定顺序执行
被保护起来的公共资源--临界资源
访问临界资源的代码被称为临界区 总结 好了以上便是本文的全部内容希望对你有所帮助或启发感谢阅读