Linux之systemV共享內(nèi)存方式
一、工作原理
操作系統(tǒng)在物理內(nèi)存上申請一塊空間,然后將申請到的空間通過頁表映射到進程地址空間mm_struct
的共享區(qū)中,然后返回虛擬地址供程序使用,如果多個進程申請的是同一塊物理空間,那么它們就可以進行通信由于同一時間可能有多組進程進行通信,所以系統(tǒng)當(dāng)中可能存在多個共享內(nèi)存塊,所以操作系統(tǒng)要把這些內(nèi)存管理起來,所以內(nèi)核中會有一個結(jié)構(gòu)體來描述共享內(nèi)存
二、系統(tǒng)調(diào)用接口
1、申請共享內(nèi)存
(一)key的獲取
#include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);
- 返回值:成功ftok 會將
proj_id
與pathname
對應(yīng)的文件信息結(jié)合起來生成最終的key值,失敗返回-1 pathname
:已經(jīng)存在的文件或目錄的路徑名proj_id
:由用戶指定的非零整數(shù),通常是一個單字符(因為只有低 8 位會被使用),
只要這兩個才參數(shù)一致,那么生成的key值就一定一致
key
值是我們少見的由我們自己傳參生成的固定的值,Linux在涉及內(nèi)存方面的時候,通常是操作系統(tǒng)代為處理,這里其實是因為假設(shè)我們有兩個進程AB,操作系統(tǒng)生成一個key
給到A,但是B也需要這個key
,但是操作系統(tǒng)是不知道哪兩個進程要建立信道進行通信的,只有程序員知道我們哪兩個進程要建立信道進行通信,因為在建立信道之前進程之間是相互獨立的,所以就不能由操作系統(tǒng)來分配key
值
(二)共享內(nèi)存的申請
shmget
函數(shù)的主要作用是在內(nèi)核中創(chuàng)建或獲取共享內(nèi)存段的標識符
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
返回值:成功返回一個非負整數(shù),即該共享內(nèi)存段的標識碼(shmid
),失敗返回-1
key
:key
是一個數(shù)字,它是不同共享內(nèi)存讓自己具備唯一性的標識size
:共享內(nèi)存大小,單位為字節(jié),一般最好是4096的整數(shù)倍,它按照頁申請,即需要4097字節(jié)就申請4096*2個字節(jié)
shmflg
是一個標識符
shmflg | 含義 | 作用 |
---|---|---|
IPC_CREAT | 創(chuàng)建標志 | 如果指定的共享內(nèi)存段不存在,就創(chuàng)建一個新的共享內(nèi)存段;若已存在,則直接返回其標識符,常用于在不確定共享內(nèi)存是否存在時創(chuàng)建或獲取它 |
IPC_EXCL | 排他標志 | 需和 IPC_CREAT 一起使用,若共享內(nèi)存已存在,shmget 調(diào)用失敗,errno 設(shè)為 EEXIST,可確保新創(chuàng)建共享內(nèi)存 |
IPC_NOWAIT | 非阻塞標志 | 當(dāng)操作不能立即完成時,不阻塞調(diào)用進程,直接返回錯誤,例如在獲取共享內(nèi)存時,若當(dāng)前資源無法立即獲取,不等待而直接返回錯誤 |
SHM_HUGETLB | 大頁標志 | 嘗試使用大頁內(nèi)存分配共享內(nèi)存段,大頁內(nèi)存可減少頁表項數(shù)量,降低內(nèi)存管理開銷,提高系統(tǒng)性能,適合處理大量數(shù)據(jù) |
SHM_NORESERVE | 不保留交換空間標志 | 不預(yù)先為共享內(nèi)存段保留交換空間,正常創(chuàng)建共享內(nèi)存時,系統(tǒng)會預(yù)留交換空間以防內(nèi)存不足,使用此標志可節(jié)省交換空間,但可能導(dǎo)致內(nèi)存緊張時出現(xiàn)頁面交換問題 |
權(quán)限標志(如 0600、0666 等) | 權(quán)限設(shè)置 | 類似文件權(quán)限設(shè)置,用于規(guī)定共享內(nèi)存段的訪問權(quán)限,0600 表示只有所有者有讀寫權(quán)限,0666 表示所有用戶都有讀寫權(quán)限 |
2、將共享內(nèi)存段連接到進程地址空間
shmat
函數(shù)的核心作用是在調(diào)用進程的虛擬地址空間和共享內(nèi)存段的物理內(nèi)存之間建立映射關(guān)系,在調(diào)用shmget
函數(shù)時,雖然創(chuàng)建或獲取了共享內(nèi)存段的標識符,但進程還不能直接訪問該共享內(nèi)存,只有通過shmat
函數(shù)將共享內(nèi)存段附加到進程的地址空間后,進程才能像訪問普通內(nèi)存一樣訪問共享內(nèi)存段中的數(shù)據(jù)
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg);
- 返回值:成功返回一個指針,指向共享內(nèi)存的起始地址,失敗返回 -1
shmid
:共享內(nèi)存段的標識符,用于指定要附加的共享內(nèi)存段,即shmget
的返回值shmaddr
:指定共享內(nèi)存段要附加到的進程地址空間的地址,如果為 NULL,則由系統(tǒng)自動選擇合適的地址shmflg
:標識符
類型 | 含義 | 作用 |
---|---|---|
SHM_RDONLY | 共享內(nèi)存只讀 | 以只讀模式將共享內(nèi)存段附加到進程的地址空間,進程只能讀取共享內(nèi)存中的數(shù)據(jù),不能進行寫操作,增強數(shù)據(jù)安全性,適用于多進程共享只讀數(shù)據(jù)的場景 |
SHM_RND | 地址舍入 | 當(dāng) shmaddr 參數(shù)不為 NULL 時,將 shmaddr 向下舍入到一個合適的內(nèi)存邊界(通常是系統(tǒng)頁面大小的整數(shù)倍),保證共享內(nèi)存的正確附加 |
SHM_REMAP | 重新映射 | 如果共享內(nèi)存段已經(jīng)被附加到進程的地址空間,使用此標志可以重新映射該共享內(nèi)存段,常用于更新共享內(nèi)存的映射關(guān)系 |
SHM_EXEC | 可執(zhí)行權(quán)限 | 允許在共享內(nèi)存段上執(zhí)行程序指令,不過并非所有系統(tǒng)都支持該標志,適用于需要在共享內(nèi)存中執(zhí)行代碼的特殊場景 |
SHM_COPY | 創(chuàng)建私有副本 | 某些系統(tǒng)支持該標志,嘗試創(chuàng)建共享內(nèi)存段的一個私有副本,后續(xù)對該副本的修改不會影響其他進程看到的共享內(nèi)存內(nèi)容,用于實現(xiàn)進程對共享內(nèi)存的獨立修改 |
SHM_ANON | 匿名共享內(nèi)存 | 部分系統(tǒng)支持該標志,用于創(chuàng)建匿名共享內(nèi)存段,此時 shmid 參數(shù)會被忽略,可結(jié)合 shmaddr 使用,常用于父子進程間的內(nèi)存共享 |
3、將內(nèi)存共享段與當(dāng)前進程脫離
當(dāng)進程調(diào)用shmat
函數(shù)將共享內(nèi)存段附加到自己的地址空間后,系統(tǒng)會在進程的虛擬地址空間和共享內(nèi)存段的物理內(nèi)存之間建立映射關(guān)系,使得進程可以像訪問普通內(nèi)存一樣訪問共享內(nèi)存,而shmdt
函數(shù)的核心作用就是解除這種映射關(guān)系
#include <sys/types.h> #include <sys/shm.h> int shmdt(const void *shmaddr);
- 返回值:成功返回0,失敗返回-1
shmaddr
:shmat
返回的指針
4、控制共享內(nèi)存
通過cmd控制共享內(nèi)存
#include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 返回值:成功返回0,失敗返回-1
shmid
:同上cmd
:將要采取的動作
類型 | 含義 | 作用 |
---|---|---|
IPC_STAT | 獲取狀態(tài)信息 | 將與 shmid 關(guān)聯(lián)的共享內(nèi)存段的當(dāng)前狀態(tài)信息復(fù)制到 buf 指向的 struct shmid_ds 結(jié)構(gòu)體中,這個結(jié)構(gòu)體包含了如共享內(nèi)存段的大小、所有者、權(quán)限、創(chuàng)建時間、最后訪問時間等信息 |
IPC_SET | 設(shè)置狀態(tài)信息 | 使用 buf 指向的 struct shmid_ds 結(jié)構(gòu)體中的值來更新與 shmid 關(guān)聯(lián)的共享內(nèi)存段的部分狀態(tài)信息,可以更新的信息包括共享內(nèi)存段的所有者、權(quán)限等 |
IPC_RMID | 刪除共享內(nèi)存段 | 標記與 shmid 關(guān)聯(lián)的共享內(nèi)存段為刪除狀態(tài),當(dāng)最后一個使用該共享內(nèi)存段的進程分離它之后,系統(tǒng)會真正釋放該共享內(nèi)存段所占用的資源,此時 buf 參數(shù)會被忽略,通常傳遞 NULL |
IPC_INFO | 獲取系統(tǒng)共享內(nèi)存信息 | 獲取系統(tǒng)范圍內(nèi)的共享內(nèi)存資源信息,這些信息會被存儲在一個由系統(tǒng)定義的特定結(jié)構(gòu)體中(通常不是 struct shmid_ds),buf 應(yīng)指向該結(jié)構(gòu)體,用于接收信息 |
SHM_INFO | 獲取共享內(nèi)存段信息 | 獲取系統(tǒng)中共享內(nèi)存段的相關(guān)統(tǒng)計信息,返回一個包含這些統(tǒng)計信息的結(jié)構(gòu)體,同樣,buf 要指向合適的結(jié)構(gòu)體來接收數(shù)據(jù) |
SHM_STAT | 通過索引獲取共享內(nèi)存狀態(tài) | 類似于 IPC_STAT,但不是通過 shmid 來指定共享內(nèi)存段,而是通過一個索引值,可以通過這種方式遍歷系統(tǒng)中的共享內(nèi)存段 |
buf
:指向一個保存著共享內(nèi)存的模式狀態(tài)和訪問權(quán)限的結(jié)構(gòu)體struct shmid_ds
三、開始通信
由于共享內(nèi)存通信沒辦法做到同步和互斥,我們通過加入命名管道的方式來形成同步與互斥的效果,普通的共享內(nèi)存通信就調(diào)用完接口直接寫直接讀就行,比較簡單,因為共享內(nèi)存是可以通過指針直接讀的,所以我們升級一下
1、comm.hpp
#ifndef __COMM_HPP__ #define __COMM_HPP__ #include <iostream> #include <string> #include <cstdlib> #include <cstring> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> #include <sys/types.h> #include <sys/stat.h> #define FIFO_FILE "./myfifo" #define MODE 0664 enum { FIFO_CREATE_ERR = 1, FIFO_DELETE_ERR, FIFO_OPEN_ERR, FIFO_READ_ERR }; using namespace std; Log log; const int size = 4096; const string pathname="/home/slm"; const int proj_id = 0x6667; //封裝一個獲取Key值的函數(shù) key_t GetKey() { //使用ftok函數(shù)獲取key值 key_t k = ftok(pathname.c_str(), proj_id); if(k < 0) { exit(1); } //返回生成的key值 return k; } //封裝一個建立共享內(nèi)存區(qū)的函數(shù) int GetShareMemHelper(int flag) { //調(diào)用獲取key值,然后在物理內(nèi)存上申請共享內(nèi)存 key_t k = GetKey(); int shmid = shmget(k, size, flag); if(shmid < 0) { exit(2); } //返回shmid return shmid; } //用于創(chuàng)建一個新的共享內(nèi)存段,如果指定的共享內(nèi)存段已經(jīng)存在,函數(shù)會失敗 int CreateShm() { return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666); } //用于獲取一個共享內(nèi)存段,如果指定的共享內(nèi)存段不存在,函數(shù)會創(chuàng)建一個新的共享內(nèi)存段;如果已經(jīng)存在,則直接返回該共享內(nèi)存段的標識符 int GetShm() { return GetShareMemHelper(IPC_CREAT); } class Init { public: Init() { // 創(chuàng)建管道 int n = mkfifo(FIFO_FILE, MODE); if (n == -1) { perror("mkfifo"); exit(FIFO_CREATE_ERR); } } ~Init() { // 銷毀管道 int m = unlink(FIFO_FILE); if (m == -1) { perror("unlink"); exit(FIFO_DELETE_ERR); } } }; #endif
2、processa.cpp
#include "comm.hpp" extern Log log; int main() { //創(chuàng)建管道 Init init; //調(diào)用 CreateShm 函數(shù)創(chuàng)建一個共享內(nèi)存段,并返回該共享內(nèi)存段的標識符shmid int shmid = CreateShm(); //調(diào)用 shmat 函數(shù)將共享內(nèi)存段附加到當(dāng)前進程的地址空間,shmid 是共享內(nèi)存段的標識符 //nullptr 表示讓系統(tǒng)自動選擇附加地址,0 表示默認附加標志 //返回值是共享內(nèi)存段在進程地址空間中的起始地址,將其強制轉(zhuǎn)換為char*類型并賦值給shmaddr char *shmaddr = (char*)shmat(shmid, nullptr, 0); //以只讀模式打開命名管道文件FIFO_FILE,由于是以只讀模式打開,該操作會阻塞 //直到有其他進程以寫模式打開同一個命名管道 int fd = open(FIFO_FILE, O_RDONLY); if (fd < 0) { exit(FIFO_OPEN_ERR); } //定義shmds用于存儲共享內(nèi)存段的狀態(tài)信息 struct shmid_ds shmds; while(true) { //定義一個字符變量c,調(diào)用read函數(shù)從命名管道文件描述符fd中讀取1個字節(jié)的數(shù)據(jù)到c中 //并將實際讀取的字節(jié)數(shù)存儲在s中,如果能讀取到說明通信進程給我們發(fā)信號了,有內(nèi)容 //那我們就讀取共享內(nèi)存部分 char c; ssize_t s = read(fd, &c, 1); if(s == 0) break; else if(s < 0) { exit(FIFO_READ_ERR); } //可以直接通過指針訪問共享內(nèi)存 cout << "client say@ " << shmaddr << endl; sleep(1); //調(diào)用shmctl函數(shù),使用IPC_STAT命令獲取共享內(nèi)存段的狀態(tài)信息,并將其存儲在shmds結(jié)構(gòu)體中 //然后將結(jié)構(gòu)體shmds部分信息打印出來 shmctl(shmid, IPC_STAT, &shmds); cout << "shm size: " << shmds.shm_segsz << endl; cout << "shm nattch: " << shmds.shm_nattch << endl; printf("shm key: 0x%x\n", shmds.shm_perm.__key); cout << "shm mode: " << shmds.shm_perm.mode << endl; } //取消鏈接 shmdt(shmaddr); //調(diào)用shmctl函數(shù),使用IPC_RMID命令將共享內(nèi)存段標記為待刪除狀態(tài) //當(dāng)所有附加進程都分離后,系統(tǒng)會自動刪除該共享內(nèi)存段 shmctl(shmid, IPC_RMID, nullptr); close(fd); return 0; }
3、processb.cpp
#include "comm.hpp" int main() { //調(diào)用GetShm函數(shù)獲取一個共享內(nèi)存段的標識符shmid //在參數(shù)相同、無特殊情況的情況下,shmid也相同 int shmid = GetShm(); char *shmaddr = (char*)shmat(shmid, nullptr, 0); int fd = open(FIFO_FILE, O_WRONLY); if (fd < 0) { exit(FIFO_OPEN_ERR); } // 一旦有了共享內(nèi)存,掛接到自己的地址空間中,直接把他當(dāng)成你的內(nèi)存空間來用即可 // 不需要調(diào)用系統(tǒng)調(diào)用接口 while(true) { cout << "Please Enter@ "; fgets(shmaddr, 4096, stdin); //在管道寫入一個字符來通知服務(wù)器共享內(nèi)存有新數(shù)據(jù) write(fd, "c", 1); } //調(diào)用shmdt函數(shù)將共享內(nèi)存段從當(dāng)前進程的地址空間分離,釋放相關(guān)的資源 shmdt(shmaddr); close(fd); return 0; }
四、其他問題
key
是在操作系統(tǒng)內(nèi)標定共享內(nèi)存唯一性的,而shmid
是在進程內(nèi)標定資源唯一性的,二者雖然都是標定唯一性,但是使用范圍不同,并且雖然共享內(nèi)存屬于文件系統(tǒng),但是shmid
和文件描述符兼容性不好,共享內(nèi)存這方面單獨搞了一套類似于文件描述符表的規(guī)則
共享內(nèi)存的生命周期隨內(nèi)核,除非用戶釋放或者內(nèi)核重啟,共享內(nèi)存才會釋放
ipcs -m
命令可以查看操作系統(tǒng)中所有的共享內(nèi)存,其中perms
是權(quán)限位,nattch
是和當(dāng)前共享內(nèi)存關(guān)聯(lián)的進程個數(shù)
共享內(nèi)存是沒有存在同步互斥這樣的保護機制的,它是所有進程通信方式中最快的,因為相較其他方式,它的拷貝更少
共享內(nèi)存內(nèi)部的數(shù)據(jù)由用戶自己維護
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Ubuntu20.04用Xshell通過SSH連接報錯的服務(wù)問題
這篇文章主要介紹了詳解Ubuntu20.04用Xshell通過SSH連接報錯的服務(wù)問題,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08linux 可執(zhí)行文件與寫操作的同步問題(文件讀寫操作產(chǎn)生的鎖機制)
在哪種系統(tǒng)下都會有文件操作產(chǎn)生的同步問題,今天說說linux下讀寫文件的鎖機制。2013-10-10linux服務(wù)器系統(tǒng)CentOS、uBuntu、Gentoo、FreeBSD、Debian的比較
最近一直都是在玩ubuntu的,但是做web服務(wù)器的CentOS還是經(jīng)常被用到,自己也糊涂了2012-12-12