一、进程间的通信方式
进程间通信就是在不同进程之间传播或交换信息,通信方式一般分为管道、命名管道(FIFO)、消息队列、信号量(Semaphore)、共享内存(Shared Memory)五种。
- 管道:只能用于父子进程或兄弟进程等有亲缘关系的通信,半双工模式,即数据只能在一个方向流动,因此有固定的读写端。
- 命名管道:可用于无关的进程间。
- 消息队列:消息具有特定的格式以及特定的优先级,可以按照消息的类型读取数据。
- 信号量:用于进程间同步,基于操作系统的 PV 操作,可以加减任意正整数,支持信号量组,一般与共享内存结合使用。
- 共享内存:多个进程共享一个给定的存储区,进程直接对内存中的数据进行存取,速度最快,但自身不具有同步机制,一般与信号量结合使用。
本篇只介绍如何使用信号量+共享内存实现队列(共享)以便于不同的进程进行数据的读写。
二、为什么共享内存传输的速度更快
上述的管道、FIFO以及消息队列都是基于内核的通信,向管道和消息队列写数据时需要先将数据从进程复制到内核中,读数据时需要从内核再复制到进程中,它们的通信方式必须借助内核来传递。然鹅,共享内存方式只需要对同一块内存进行读写,这块内存指的是:多个不同的虚拟地址通过页表映射到物理空间的同一区域。没有什么比直接操作内存更香的了~
三、共享内存+信号量实现共享队列
1.共享内存的实现
class SharedMemory {
public:
SharedMemory(int memoryKey); //传入Key
virtual ~SharedMemory();
int findMemory(); //查找Id对应的内存空间是否存在
int getMemory(long long memorySize); //申请内存空间
char *mountMemory(); //将进程与存储空间进行链接,返回共享内存的头指针
int delMount(char *shmAddress); //脱离已经连接上的共享内存
int delMemory(); //释放内存空间
private:
int memory_key_; //申请内存空间时需要传入一个Key值
int memory_id_; //根据Key值申请的空间返回该内存的唯一Id,后续操作内存时根据Id进行查找
};
SharedMemory::SharedMemory(int memoryKey) {
memory_key_ = memoryKey;
memory_id_ = -1;
}
int SharedMemory::findMemory() {
memory_id_ = shmget(memory_key_, 0, 0); //id赋值,不存在,返回-1
if (memory_id_ == -1) {
return false;
}
return true;
}
int SharedMemory::getMemory(long long memorySize) {
if (memory_id_ == -1) { // 申请空间
long long tempSize = memorySize;
long long formatSize = 1; // 空间大小为小于等于memorySize的最大二次幂
while (tempSize >>= 1) {
formatSize <<= 1;
}
memorySize = (formatSize < memorySize) ? formatSize << 1 : formatSize;
memory_id_ = shmget(memory_key_, memorySize, IPC_CREAT); //该函数成功返回id,失败返回-1,后两个参数若全为0表示只检查不创建
if (memory_id_ == -1) {
return false;
}
}
return true;
}
int SharedMemory::delMount(char *shmAddress) {
if (shmdt(shmAddress) == -1) {
return false;
}
return true;
}
char *SharedMemory::mountMemory() {
return static_cast<char *>(shmat(memory_id_, NULL, 0));
}
int SharedMemory::delMemory() {
if (shmctl(memory_id_, IPC_RMID, 0) == -1) {
return false;
}
return true;
}
2.信号量的实现
typedef union semun {
int val;
} SEMUN; //设置信号量的参数,只用到了Val
class SemaphorePV {
public:
SemaphorePV();
int getSem(int semPhoreKey); //传Key值获取信号量
int mountSem(); //给val赋值并挂载
int pSem(); //P操作
int vSem(); //V操作
int delSem(); //删掉信号量
private:
int semaphore_id_; //根据Key值生成的id
};
SemaphorePV::SemaphorePV() {
semaphore_id_ = -1;
}
int SemaphorePV::getSem(int semPhoreKey) {
if (semaphore_id_ == -1) {
semaphore_id_ = semget(semPhoreKey, 1, IPC_CREAT | 0644); //获取id
if (semaphore_id_ == -1) {
return false;
}
}
return true;
}
int SemaphorePV::mountSem() {
SEMUN sem_union;
sem_union.val = 1; //初始化1
if (semctl(semaphore_id_, 0, SETVAL, sem_union) == -1) {
return false;
}
return true;
}
int SemaphorePV::pSem() {
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO; //该标记表示若有其中一个进程崩溃掉,则会释放掉持有的资源
if (semop(semaphore_id_, &sem_b, 1) == -1) { // nsops(参3) 指向操作信号量的个数
return false;
}
return true;
}
int SemaphorePV::vSem() {
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;
if (semop(semaphore_id_, &sem_b, 1) == -1) {
return false;
}
return true;
}
int SemaphorePV::delSem() {
if (semctl(semaphore_id_, 0, IPC_RMID, 0) == -1) {
return false;
}
return true;
}
3.信号量的封装
进行封装的目的是为了在调用LockPV的构造函数时就进行P操作,析构函数进行V操作。这样往队列里读写数据时时只需要在读写函数里创建一个LockPV对象就可以了,跳出函数时该进程会自动释放掉持有的资源。
class LockPV {
public:
LockPV(int key); //传入Key
virtual ~LockPV();
int pResource();
int vResource();
private:
int key_;
std::shared_ptr<SemaphorePV> semaphorePV_; //存入信号量的共享指针
};
LockPV::LockPV(int key) {
key_ = key;
semaphorePV_ = std::make_shared<SemaphorePV>();
if(!pResource()) std::cout<<"PResource has err!"<<std::endl;
}
LockPV::~LockPV() {
if (!vResource() != WK_OK) std::cout<<"VResource has err!"<<std::endl;
}
int LockPV::pResource() {
if (!semaphorePV_) {
semaphorePV_ = std::make_shared<SemaphorePV>();
}
if(!semaphorePV_->getSem(key_)||!semaphorePV_->mountSem()||!semaphorePV_->pSem()) return false;
return true;
}
int LockPV::vResource() {
if(!semaphorePV_->vSem()) return false;
return true;
}
4.队列的实现
typedef struct shmhead {
int read_index; // 读入数据索引
int write_index; // 写数据索引
} SHARE_QUEUE_HEAD; //队列的头部
class SharedQueue {
public:
SharedQueue(int memoryKey, int blocksSize, int blocksNum); //key,块的个大小,块的个数
virtual ~SharedQueue();
int openQueue();
int closeQueue();
int isOpen(); //判断是否打开
int isFull(); //是否满
int isEmpty(); //是否空
int getQueueMember(); //获取队列成员个数
int enQueue(char *data, int dataLength); //写数据
int deQueue(char *data, int dataLength); //读数据
int rmQueue();
private:
SHARE_QUEUE_HEAD *queue_head_;
char *data_load_; //数据索引
int memory_key_;
long long memory_length_;
int blocks_num_;
int blocks_size_;
int initialize_; // 是否初始化
int lock_id_;
};
SharedQueue::SharedQueue(int memoryKey, int blocksSize, int blocksNum) {
memory_key_ = memoryKey;
lock_id_ = memory_key_;
blocks_num_ = blocksNum+1; //循环队列,留一个空间不存数据,用来区分队列的空和满
blocks_size_ = blocksSize;
memory_length_ = sizeof(queue_head_) + blocks_size_ * blocks_num_;
initialize_ = false;
openQueue();
}
SharedQueue::~SharedQueue() {
if (closeQueue() != true)
std::cout<<"~Close queue has error!"<<std::endl;
}
int SharedQueue::openQueue() {
SharedMemory sharedMemory(memory_key_);
int findMemory = sharedMemory.findMemory();
if (findMemory == false) {
if(!sharedMemory.getMemory(memory_length_)) return false; //找不到就申请
}
queue_head_ = reinterpret_cast<SHARE_QUEUE_HEAD *>(sharedMemory.mountMemory()); //将获取到的内存空间与队列头部进行挂载
data_load_ = (WK_UINT8 *) queue_head_ + sizeof(SHARE_QUEUE_HEAD); //数据索引后移
if (findMemory == false) { //未找到即第一次申请时把读写索引全部置为0
queue_head_->read_index = 0;
queue_head_->write_index = 0;
}
initialize_ = true;
return true;
}
int SharedQueue::closeQueue() {
if(isOpen()==false){
return false;
}
SharedMemory sharedMemory(memory_key_);
if (sharedMemory.findMemory()) {
if(!sharedMemory.delMount(reinterpret_cast<WK_UINT8 *>(queue_head_))) return false;
} //关闭队列时只取消挂载,内存空间不变,读写索引位置不变
queue_head_ = nullptr;
data_load_ = nullptr;
initialize_ = false;
return true;
}
int SharedQueue::isOpen() {
return initialize_;
}
int SharedQueue::enQueue(char *data, int dataLength) {
LockPV lockPv(lock_id_); // P操作上锁
if (isOpen() == false) {
return false;
}
if (isFull() == true) {
return false;
}
if(dataLength > blocks_size_) return false;
char *putPlace = data_load_ + queue_head_->write_index * blocks_size_; //通过写索引获取数据
memcpy(putPlace, data, dataLength);
queue_head_->write_index = (queue_head_->write_index + 1) % (blocks_num_); //写索引后移,循环队列需进行取余操作
return true; //退出函数时,lovkPV对象销毁,进程释放资源,其他进程可进行读写操作
}
int SharedQueue::deQueue(char *data, int dataLength) {
LockPV lockPv(lock_id_); // P操作上锁
if (isOpen() == false) {
return false;
}
if (isEmpty() == true) {
return false;
}
if(dataLength > blocks_size_) return false;
char *outPlace = data_load_ + queue_head_->read_index * blocks_size_; //通过读索引获取数据
memcpy(data, outPlace, dataLength);
queue_head_->read_index = (queue_head_->read_index + 1) % (blocks_num_);// 读索引后移
return true;
}
int SharedQueue::isFull() {
if ((queue_head_->write_index + 1) % blocks_num_ == queue_head_->read_index) {
return true;
}
return false;
}
int SharedQueue::isEmpty() {
if (queue_head_->write_index == queue_head_->read_index) {
return true;
}
return false;
}
int SharedQueue::getQueueMember() {
return (queue_head_->write_index - queue_head_->read_index + blocks_num_) % blocks_num_; //循环队列获取成员个数
}
后记
赶着九月的尾巴终于把一直拖的东西搞完了!中秋Happy!!!国庆Happy!!!