程式間通訊——基於共享記憶體和訊號量實現共享佇列

zzti_Lmh發表於2020-09-30

 


一、程式間的通訊方式

程式間通訊就是在不同程式之間傳播或交換資訊,通訊方式一般分為管道、命名管道(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(WK_UINT8 *data, WK_INT32 dataLength); //寫資料

    int deQueue(WK_UINT8 *data, WK_INT32 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(WK_UINT8 *data, WK_INT32 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(WK_UINT8 *data, WK_INT32 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!!!

相關文章