文章目錄
- 共享內存原理
- 申請共享內存函數(shmget)
- 參數key
- 生成key值示例
- 申請共享內存
- 掛接到進程地址空間函數(shmat)
- 去關聯函數(shmdt)
- 控制共享內存(shmctl)
- IPC_STAT
- IPC_RMID
- ipcs
- 其余進程獲取該共享內存
- 進程間通信
進程間通信:IPC,Inter Process Communication
共享內存原理
進程之間通信的本質是:讓不同的進程,看到同一份資源。
無論是匿名管道還是命名管道的通信,我們都是在內核空間里的緩沖區進行實現的進程通信,對于這種方式在用戶輸入時我們在內核空間緩沖區中進行通信,然后再寫入物理內存,那么我們是否可以做到直接讓進程在物理內存中進行進程間的通信呢?
每一個進程都有一個虛擬內存(地址),他們通過頁表將虛擬地址映射到物理地址上,因此我們可以讓操作系統幫我們在物理內存申請一段空間,然后通過頁表把這份空間映射到虛擬內存的共享區,這樣我們就做到了讓不同的進程看到了同一份資源。
申請共享內存函數(shmget)
shmget:shared memory get,就是獲取共享內存
參數:
key:這個共享內存段名字
size:需要創建的共享內存的大小
shmflg:通過類似位圖實現的多操作整數
IPC_CREAT(單獨):如果你申請的共享內存不存在,存在,就獲取返回
IPC_CREAT | IPC_EXCL:如果你申請的共享內存不存在,就創建,存在,就出錯返回。這是為了確保我們申請的共享內存,一定是一個新的,沒有被別人使用
IPC_EXCL:不單獨使用
這段共享內存的權限,千萬不要忘記或上 | 權限,不然后面就會導致進程掛接不上
//申請共享內存
int shmid = shmget(key, NUM, IPC_CREAT | 0666);//返回值為共享內存標識符
返回值:
共享內存標識符
參數key
key是一個數字,這個值是隨機的,關鍵是這個隨機數在內核中具有唯一性,可以讓他對不同的進程擁有唯一的標識,第一個進程創建出key之后,后面的進程只要有了這個key,就可以拿著這個key去和其它進程進行共享內存了。舉個例子:這個key就相當于是一把鑰匙,而這個共享內存就是一把鎖,只要不同的進程拿著這個鑰匙,就可以打開這個內存了。那么現在的問題是我們怎么去生成這個key值呢,又這么保證它在內核中具有唯一性呢?
系統中什么具有唯一性呢?對,就是路徑,路徑就有唯一性,因此我們可以通過一個函數把路徑作為參數傳給這個函數,函數根據一定的算法幫助我們生成這個key。
庫里面給我們提供了生成key的函數:ftok()
參數:
pathname:文件路徑
proj_id:項目id,我們給定的一個int值,目的就是為了和文件路徑一起通過一系列的算法生成一個唯一key值
作用:ftok函數通過給的路徑和int值就可以幫我們生成一個在內核中具有唯一性的值。
返回值:
成功返回key,失敗返回-1并設置錯誤碼
key存在哪里呢?系統中一定有很多的共享內存,操作系統要想把他們管理起來,就要先描述再組織,因此key一定被存在共享內存的描述對象中。
生成key值示例
#include <sys/types.h>
#include <sys/ipc.h>
#include <iostream>using namespace std;#define PATHNAME "./common.cc"
#define PROJ_ID 0x8888int main()
{key_t key = ftok(PATHNAME, PROJ_ID);if(key == -1){perror("ftok error");exit(0);}cout << key << endl;return 0;
}
所給的路徑,一般是當前文件的路徑,當然也可以給其他文件的,但為了保證唯一性我們一般在哪個路徑下生成key就使用哪個路徑,但前提是所給的文件路徑一定是存在的,如果不存在就會生成失敗。
申請共享內存
//申請共享內存,方式1
int shmid = shmget(key, 1024, IPC_CREAT 0666);
//申請共享內存,方式2
int shmid = shmget(key, 1024, IPC_CREAT|IPC_EXCL 0666);
問題:我們繞了一大圈生成的這個shmid和key都是具有唯一性的,那么我們為什么不直接用key呢?非要再去通過給shmget函數傳key生成呢?
key是用于在操作系統內標定唯一性,而shmid是共享內存標識符,只存在于我們的進程內,用來表示資源的唯一性
共享內存的大小一般是4096(4KB)的整數倍,雖然我們這里申請的是1024,其實系統給我們的就是4096,因為操作系統是按4KB為單位對我們進行分配的,需要注意的是我們只能用1024,多給的我們不可以使用,用了就是越界可能會出現錯誤
掛接到進程地址空間函數(shmat)
shmat:shared memory attach
返回值為掛接到虛擬地址中的哪個位置
上面我們只是在物理內存中申請了一份共享內存,但是我們還要去把該共享內存掛接到進程地址空間的共享區中,如何做到呢?通過shmat函數來實現
參數:
shmid:就是申請共享內存函數shmget的返回值,共享內存標識符
shmaddr:讓申請的共享內存掛到共享區的哪一個位置(地址),設置成nullptr就是讓系統進行決定
shmflg:進程以什么方式去掛接這個共享內存
shmaddr為NULL,核心自動選擇一個地址
shmaddr不為NULL且shmflg無SHM_RND標記,則以shmaddr為連接地址。
shmaddr不為NULL且shmflg設置了SHM_RND標記,則連接的地址會自動向下調整為SHMLBA的整數倍。公式:shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示連接操作用來只讀共享內存
返回值:返回一個void類型的指針,指向在物理內存中申請的共享內存在虛擬地址中共享內存的起始地址
共享內存:指在物理內存中申請的共享內存
共享區:指在虛擬內存中的共享區域
去關聯函數(shmdt)
dt:delete touch
給虛擬內存共享區掛接的物理內存,如果進程結束,系統會自動取消關聯,但是我們現在不想等到進程結束,想要在進程還在運行時就去關聯,怎么做呢?
通過函數shmdt()就可以做到去關聯
參數:
const void* shmaddr:就是在物理內存申請的共享內存掛接到共享區的起始地址,也就是函數shmat的返回值。
返回值:
成功返回0,失敗返回-1,并設置錯誤碼
注意:將共享內存段與當前進程脫離不等于刪除共享內存段。
//去關聯
shmdt(shmaddr);
控制共享內存(shmctl)
參數:
shmid:共享內存標識符,就是我們通過shmget函數得到的返回值。
cmd:控制這個函數去做什么
buf:指向一個保存著共享內存的模式狀態和訪問權限的數據結構
返回值:
成功返回0,失敗返回-1
其中參數cmd有三個取值,如下:
IPC_STAT
用于獲取管理該共享內存結構中的數據,這個共享內存的key值,關聯個數等數據
struct shmid_ds 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;
IPC_RMID
刪除共享內存選項
shmdt函數只是把進程和該共享內存的關系從也表中刪除從而取消關聯,但是我們在物理內存中申請的共享內存并沒有被釋放,因此我們可以通過shmctl來釋放對應的共享內存
ipcs
如果不通過shmctl來釋放對應的共享內存,那么即使進程結束,該共享內存仍然是被占用的,因此我們可以手動的來釋放該共享內存,操作如下:
此時只是去關聯了,但是該共享內存仍然被占用沒有被釋放
通過ipcrm -m選項 + 對應的共享內存標識符來釋放該共享內存
其余進程獲取該共享內存
只需要創建一次共享內存,其余的進程只需拿著這個共享內存的標識符就可以得到這個共享內存了
#include <sys/types.h>
#include <sys/ipc.h>
#include <iostream>
#include <sys/shm.h>
#include <unistd.h>#include "log.hpp"using namespace std;#define PATHNAME "/home/Linux3"
#define PROJ_ID 0x6666
#define NUM 1024key_t GetKey()
{//生成參數唯一key值key_t key = ftok(PATHNAME, PROJ_ID);if(key == -1){perror("ftok error");exit(0);}return key;
}int GetShareMemHelper(int flag)
{key_t key = GetKey();int shmid = shmget(key, NUM, flag);//返回值為共享內存標識符return shmid;
}//創建第一個共享內存
int CreatShm()
{return GetShareMemHelper(IPC_CREAT |IPC_EXCL | 0666);
}//獲取已經創建好的共享內存
int GetShm()
{return GetShareMemHelper(IPC_CREAT);
}
#include "common.hpp"int main()
{int shmid = GetShm();//自定義函數,用于獲取已經創建好的共享內存char* shmaddr = (char*)shmat(shmid, nullptr, 0);sleep(5);shmdt(shmaddr);sleep(2);shmctl(shmid, IPC_RMID, nullptr);return 0;
}
進程間通信
此時我們就可以向該共享內存寫入了,而不是先寫入緩沖區,再講緩沖區數據拷貝到內存,其它進程就直接從該共享內存進行讀取就可以了。
但是此時會有一個問題,就是共享內存對于不同進程而言不是同步的,即寫進程還沒寫,讀進程就從共享內存中讀,此時讀到的為空,或者上次的,就一直在那打印,因此我們要讓進程同步,等寫進程寫入了,讀進程才開始讀,這里舉個例子,我們可以通過管道來實現進程同步,因為read函數,如果沒有讀取到就會一直等待
char c = 'c';
while(1)
{fgets(shmaddr, 1024, stdin);write(fd, &c, 1);//向命名管道寫入信息,用來通知讀進程可以從共享內存中讀取了//通過命名管道使得進程a,b同步}
Init init;
int fd = open(FIFO_FILE, O_RDONLY);
char c;
while (1)
{ssize_t n = read(fd, &c, 1);//等待通知信息,如果沒有就退出if(n <= 0){break;}cout << shmaddr;
}
創建一個管道,當向共享內存寫入時,該進程通過write函數向管道中寫入數據,對于讀進程如果一直沒有read到,就會一直等待,當讀到了才會向下執行代碼,這里管道的作用就是為了實現進程同步。