程式間通訊——基於共享記憶體和訊號量實現共享佇列
一、程式間的通訊方式
程式間通訊就是在不同程式之間傳播或交換資訊,通訊方式一般分為管道、命名管道(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!!!
相關文章
- 程式間通訊之共享記憶體記憶體
- Linux程式間通訊之共享記憶體Linux記憶體
- (原創)[.Net] 程式間通訊框架(基於共享記憶體)——SimpleMMF框架記憶體
- 程序間通訊(3)-共享記憶體記憶體
- Linux 程式間通訊之System V 共享記憶體Linux記憶體
- php實現共享記憶體程式通訊函式之_shmPHP記憶體函式
- Python中使用共享變數+訊號量實現程序間的實時通訊Python變數
- Android native程式間通訊例項-binder結合共享記憶體Android記憶體
- system-v IPC共享記憶體通訊記憶體
- 程式間的八種通訊方式----共享記憶體是最快的 IPC 方式記憶體
- 程式間通訊--訊息佇列佇列
- Linux系統程式設計之程式間通訊方式:共享記憶體例項演示Linux程式設計記憶體
- 20.2、python程式間通訊——佇列和管道Python佇列
- ORACLE在各作業系統訊號量與共享記憶體的維護Oracle作業系統記憶體
- 畫江湖之 PHP 多程式開發 [程式中如何通訊 共享記憶體]PHP記憶體
- 畫江湖之 PHP 多程式開發 【程式中如何通訊 共享記憶體】PHP記憶體
- Linux 下的程式間通訊:使用管道和訊息佇列Linux佇列
- 程式間通訊——XSI IPC之訊息佇列佇列
- Linux 下的程式間通訊:共享儲存Linux
- 程式間通訊——POSIX 有名訊號量與無名訊號量
- GaussDB(DWS)中共享訊息佇列實現的三大功能佇列
- linux 程式間通訊之System V 訊息佇列Linux佇列
- 程序間通訊(2)-訊息佇列佇列
- Linux 程式間通訊之System V 訊號量Linux
- Kubernetes中Pod間共享記憶體方案記憶體
- 程序間通訊(4)-訊號量
- swoole4.5.2和hyperf2.0如何實現共享佇列佇列
- 資料庫實現原理#6(共享記憶體)資料庫記憶體
- Golang 共享記憶體Golang記憶體
- POSIX 共享記憶體記憶體
- [Linux]共享記憶體Linux記憶體
- Linux系統程式設計之程式間通訊方式:訊息佇列Linux程式設計佇列
- PHP基於Redis訊息佇列實現的訊息推送的方法PHPRedis佇列
- 基於訊息佇列(RabbitMQ)實現延遲任務佇列MQ
- OpenResty 和 Nginx 的共享記憶體區是如何消耗實體記憶體的RESTNginx記憶體
- Linux 下的程式間通訊:套接字和訊號Linux
- Qt共享記憶體QSharedMemoryQT記憶體
- Linux共享記憶體(二)Linux記憶體