进程间通信——基于共享内存和信号量实现共享队列

 


一、进程间的通信方式

进程间通信就是在不同进程之间传播或交换信息,通信方式一般分为管道、命名管道(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!!!

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值