文章目錄
- 共享內存(Share Memory)
- 信號隊列(Message Queue)
- 信號量(semaphore)
進程間通信的核心理念:讓不同的進程看見同一塊資源
linux下的通信方案: SYSTEM V
共享內存(Share Memory)
特點:1.共享內存是進程見通信最最快的
?????????? 2.可以提供較大通信空間
注意:共享內存由于裸露給所有使用者,因此是需要維護的
做法:去申請一塊空間,讓其映射到對應的不同進程的進程地址空間。
如圖:
那么具體是怎么做的呢?
- linux是生成一個特定的key,有key作為表示這塊共享內存的唯一標識。
- 不同進程在運行的時候憑借這個key拿到共享內存的shmid(類似于文件管理系統的fd),進行掛接到自己的進程地址空間上。
- 申請的空間是不會自己釋放的,要么在程序里面用funtion控制,要么在外部手動釋放
-
ipcs -m
-
#查看當前有哪些共享內存
-
ipcrm -m shmid
-
#刪除對應的共享內存id
需要用到的系統調用:
shmget #創建共享內存
shmat # 掛接共享內存
shmdt # 取消掛接
shmctl # 操控這塊共享內存
unlink # 刪除文件
server:
#include "Common.hpp"class Init
{
public:Init(){bool r = MakeFifo();//用管道是為了進程進行時,具備一定順序性。if (!r)return;key_t key = GetKey();shmid = CreatShm(key);std::cout << "shmid:" << shmid << "\n";// sleep(5);std::cout << "開始將shm映射到進程地址空間\n";s = (char *)shmat(shmid, nullptr, 0);fd = open(filename.c_str(), O_RDONLY);}~Init(){close(fd);std::cout << "將shm從進程地址空間移除\n";shmdt(s);std::cout << "將共享內存從操作系統中釋放\n";shmctl(shmid, IPC_RMID, nullptr);unlink(filename.c_str());}int FileDirection(){return fd;}const char *ShnPtr(){return s;}private:int shmid;int fd;char *s;
};int main()
{Init init;// struct shmid_ds ds;// std::cout<<std::hex<<ds.shm_perm.__key<<"\n";// std::cout<<ds.shm_nattch<<"\n";while (true){int code = 0;ssize_t n = read(init.FileDirection(), &code, sizeof(code));if (n > 0){std::cout << "共享讀取:" << init.ShnPtr() << "\n";}else if (n == 0){break;}else{std::cerr << "讀取錯誤,錯誤碼:" << errno << "\n";}}return 0;
}
client
#include "Common.hpp"int main()
{key_t key = GetKey();int shmid = CreatShmHelper(key, IPC_CREAT | 0644);char *s = static_cast<char *>(shmat(shmid, nullptr, 0));std::cout << "attach shm done\n";int fd = open(filename.c_str(), O_WRONLY);for (int c = 0; c < 26; c++){s[c] = c + 'a';std::cout << "write:" << (char)c + 'a' << "done\n";sleep(1);int code = 1;write(fd, (char *)&code, sizeof(code));}// sleep(5);std::cout << "dettach shm done\n";shmdt(s);close(fd);return 0;
}
.h
#pragma once#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <fcntl.h>const std::string pathname = "/home/fuh_cs/Desktop/cpp_learning/linux/ShareMemory/Common.hpp";
const int proj_id = 0x234;
const int size = 4096;
const std::string filename = "fifo";
key_t GetKey()
{key_t key = ftok(pathname.c_str(), proj_id);if (key < 0){std::cerr << "errno:" << errno << ",errnostring:" << std::strerror(errno) << std::endl;exit(-1);}return key;
}int CreatShmHelper(key_t key, int flag)
{int shmid = shmget(key, size, flag); //共享內存也有權限也是需要設置的// EXCL保證創建時如果存在就會失敗if (shmid < 0){std::cerr << "errno:" << errno << ",errnostring:" << std::strerror(errno) << std::endl;exit(2);}return shmid;
}int CreatShm(key_t key)
{return shmget(key, size, IPC_CREAT | IPC_EXCL | 0644); //共享內存也有權限也是需要設置的
}int GetShm(key_t key)
{return shmget(key, size, IPC_CREAT); //共享內存也有權限也是需要設置的
}//為1創建成功
bool MakeFifo()
{int n = mkfifo(filename.c_str(), 0666);if (n < 0){std::cerr << "errno:" << errno << ",errstring" << strerror(errno) << std::endl;return 0;}std::cout << "mkfifo success..." << std::endl;return 1;
}
信號隊列(Message Queue)
基于SYSTEM V的還有對應的信號隊列(Message Queue),信號量
下面展示下Message Queue的簡單使用代碼。
基本的系統調用函數,在下面代碼中有,具體使用可以通過man手冊查詢。
#pragma once#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <fcntl.h>
#include <sys/msg.h>
key_t GetKey()
{key_t key = ftok(pathname.c_str(), proj_id);if (key < 0){std::cerr << "errno:" << errno << ",errnostring:" << std::strerror(errno) << std::endl;exit(-1);}return key;
}//消息隊列也是存在于內核之中,不手動關閉的生命周期和內核一起int main()
{key_t key = GetKey();int msgid = msgget(key,IPC_CREAT |IPC_EXCL);std::cout<<"msgid:"<<msgid<<'\n';struct msqid_ds ds;std::cout<<ds.__msg_cbytes<<'\n';std::cout<<ds.msg_perm.__key<<'\n';//用msgsend來發送消息//用msgrcv來接受消息msgctl(msgid,IPC_RMID,nullptr);//也可以 ipcrm -q msgid 在bash刪除return 0;}
信號量(semaphore)
前置知識:
- 公共資源:多個執行流看見的同一份資源
- 多個執行流訪問同一份資源,就存在并發訪問
- 為了解決并發訪問公共資源的問題,導致的數據不一致、臟讀等問題,需要保護資源
- 因此引發出互斥和同步
- 互斥:同一時刻,只能有一個執行流訪問資源,加鎖完成
- 同步:多個執行流按照預定的先后次序來訪問公共資源
- 被保護起來的資源,稱為臨界資源
- 訪問臨界資源的執行流(或者代碼),稱為臨界區
分析: - 本質是個計數器
- 當進程需要訪問公共資源,先獲取信號量,再去訪問資源。(相當于信號量是獲取資源的憑證,有了信號量,就一定會有資源)沒獲取信號量的進程,就阻塞等待。
- 信號量如果被獲取了,就沒了,沒被獲取,就全部都在。是只有兩種狀態,二元性的。因此由此二元性,完成了互斥的功能
- 不同的進程也需要看到同一份信號量,因此信號量也被納入IPC體系,也就是說,信號量由操作系統提供
- 我們知道SYSTEM V的資源是可以看見的,但是信號量是原子的(atomic,不可在分的),即使被進程競爭的訪問,也只會出現要么獲取了,要么沒獲取信號量。不會出現獲取半個的情況。這種原子的獲取操作稱之為P操作。相對應原子的釋放,稱之為V操作。
信號量系統調用
semget
semctl
semop
linux內核看SYSTEM V設計的共享內存等通信方式
一般來說:
- 內核里面有一個ipc_id_ary,其是一個柔性數組,存儲了一個size表示大小和指針數組,size表示指針數組的個數
- 這個指針數組所存的指針類型是 **kern_ipc_perm***的指針類型
- 由SYSTEM V標準下設計出來的共享內存,信號隊列等,內核里面都是由一個自己類型的結構體去管理的(例如:msg_queue,就是管理信號隊列的結構體,Shmid_Kernel, 就是管理共享內存的結構體)
- 而這些結構體的第一個元素,都被設計成相似的結構體(信號隊列是:q_perm,共享內存是:shm_perm),這些結構體所包含的元素類型,其實和 kern_ipc_perm的結構體元素類型是一樣的。
- 這就使得我們存儲kern_ipc_perm的指針,即使存了msg_queue的結構體指針,只需要通過強制類型轉換,也是可以訪問msg_queue結構體
- 這樣對SYSYTEM V設計下的共享內存,信號隊列等可以統一管理
上面這種內核的設計:實際就是利用了C語言的特點,實現了多態,有種通過父類指針訪問子類的類似