Linux SystemV通信包括:
共享記憶體,訊息佇列,信號量
其中信號量為了行程的同步與互斥而設計的
共享記憶體和訊息佇列為了行程間傳遞資料設計
這里討論其中之一的共享記憶體
文章目錄
- 1.共享記憶體原理
- 2共享記憶體建立與釋放的程序
- ①申請共享記憶體
- shmget函式(sys/ipc.h -sys/shm.h)(創建共享記憶體)
- 保證共享記憶體的唯一性ftok函式的代碼(sys/types.h sys/ipc.h)
- shmflg:創建共享記憶體的選項,
- 命令查看共享記憶體(ipcs -m)
- ②共享記憶體掛接到行程空間(建立映射關系)
- shmat函式(sys/types.h -sys/shm.h)
- ③去關聯共享記憶體(取消映射關系)
- shmdt函式(sys/type.h -sys/shm.h)
- ④釋放共享記憶體(將資源回傳給作業系統)
- 命令歸還申請的共享記憶體ipcrm -m)
- 呼叫(shmctl)函式(共享記憶體控制)歸還共享記憶體(sys/ipc.h -sys/shm.h)
- 3.共享記憶體的使用
- 模擬client行程與sever行程通信
- 4.共享記憶體通信與管道通信的區別
1.共享記憶體原理
我們在創建行程時Linux作業系統會創建mm_struct行程地址空間,行程地址空間通過頁表映射到物理記憶體上,
當兩個行程的mm_struct行程地址空間通過頁表指向同一塊物理記憶體時,就實作了共享記憶體的程序,
注意:要和父子行程寫時拷貝做區別,父子行程在沒有修改資料時指向同一塊物理空間,但當父子行程有資料修改時,作業系統會修改頁表使其指向不同的物理空間,

2共享記憶體建立與釋放的程序
①申請共享記憶體
shmget函式(sys/ipc.h -sys/shm.h)(創建共享記憶體)
原型:
int shmget(key_t key, size_t size,int shmflg);
引數:
key:這個共享記憶體段名字
size:共享記憶體大小
shmflg:創建共享記憶體的選項,
回傳值:成功回傳一個非負整數,即該共享記憶體段的標識碼(共享記憶體句柄),失敗回傳-1,在用戶層標識共享記憶體
保證共享記憶體的唯一性ftok函式的代碼(sys/types.h sys/ipc.h)
為了保證共享記憶體的唯一性,在shemget函式傳入的第一個引數key來保證共享記憶體唯一,這個唯一的值,表明這個是新創建的共享記憶體,否則說明這個共享記憶體存在了,
下面用這個生成唯一序列

pathname:路徑名 proj_id:資料,可以任意指定
如果生成序列成功,回傳這個序列,失敗回傳-1
呼叫函式ftok函式,傳入路徑和整形,系統會根據演算法生成序列,我們用這個序列來標定共享記憶體,如果生成的序列不唯一,則修改路徑或整數直到唯一
commen.h
1 #pragma once
2
3 #include<stdio.h>
4 #include<sys/shm.h>
5 #include<sys/types.h>
6 #include<sys/ipc.h>
7
8 #define PATH "/home/dodamce/SharePace"//任意路徑
9
10 #define PROJ_ID 0x666//任意值
11
12 #define SIZE 4096//一個記憶體頁大小
sever.c
1 #include"commen.h"
2
3 int main()
4 {
5 key_t key=ftok(PATH,PROJ_ID);
6 if(key<0)
7 {
8 printf("Error\n");
9 }
10 else
11 {
12 printf("%d\n",key);
13 }
14 return 0;
15 }

如上圖,我們生成了一個唯一的序列
shmflg:創建共享記憶體的選項,
- IPC_CREAT:如果存在共享記憶體,直接回傳這塊空間,如果這塊空間不存在,先創建再回傳這塊空間,所以這個選項呼叫成功后一定可以獲得一塊共享記憶體,但是無法保證是否為新的共享記憶體,
- IPC_EXCL :不能單獨使用,無意義,經常與IPC_CREAT一起使用,
- IPC_CREAT| IPC_CREAT:創建新的共享記憶體,如果這個共享記憶體存在了出錯回傳,上面的選項呼叫成功一定會生成一塊新的共享記憶體
- 還可以設定共享記憶體權限,這個權限設定與檔案權限設定相同為8進制數字,默認為0,無任何權限
#include"commen.h"
int main()
{
key_t key=ftok(PATH,PROJ_ID);
if(key<0)
{
perror("Error\n");
return 1;
}
int shm=shmget(key,SIZE,IPC_CREAT|IPC_EXCL);//創建一個全新的共享記憶體
if(shm<0)
{
perror("Shmget error\n");
return 2;
}
return 0;
命令查看共享記憶體(ipcs -m)

注意:行程退出了,但是共享記憶體不會自動釋放,
perms為共享記憶體權限,當在shmget沒有設定時為默認值0
nattch為使用這塊共享記憶體的行程數


當我們在運行程式時發現共享記憶體已經存在了

這點與管道通信不同,管道生命周期隨行程,行程退出管道自動洗掉,
但行程退出,共享記憶體不會釋放,它的生命周期隨內核,
②共享記憶體掛接到行程空間(建立映射關系)
shmat函式(sys/types.h -sys/shm.h)

引數解釋:
shmid:共享記憶體在用戶區的編號,
shmaddr:將共享記憶體映射到行程地址空間的哪一個位置,不關心可以設為NULL由作業系統設定,
shmflg:掛接共享記憶體時設定的屬性,
eg:SHM_RDONLY設定為只能讀取共享記憶體的資料,設定為0表示可讀可寫,
回傳值:
成功掛接回傳映射到行程地址空間(虛擬地址)的首地址,失敗回傳-1;
注意:在進行共享記憶體掛接時共享記憶體一定有權限,否則掛接失敗
#include"commen.h"
int main()
{
key_t key=ftok(PATH,PROJ_ID);
if(key<0)
{
perror("Error\n");
return 1;
}
int shm=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);//創建一個全新的共享記憶體
if(shm<0)
{
perror("Shmget error\n");
return 2;
}
printf("attach begin:\n");
shmat(shm,NULL,0);//掛接共享記憶體
sleep(5);
printf("attach end\n");
sleep(1);
printf("end\n");
shmctl(shm,IPC_RMID,NULL);//歸還共享記憶體
return 0;
}
運行后查看共享記憶體ipcs -m可以看到申請的共享記憶體已有一個行程掛接

③去關聯共享記憶體(取消映射關系)
shmdt函式(sys/type.h -sys/shm.h)

通過上一步共享記憶體映射到虛擬空間地址就可以取消與共享記憶體的映射
#include"commen.h"
int main()
{
key_t key=ftok(PATH,PROJ_ID);
if(key<0)
{
perror("Error\n");
return 1;
}
int shm=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);//創建一個全新的共享記憶體
if(shm<0)
{
perror("Shmget error\n");
return 2;
}
printf("attach begin:\n");
char* begin= shmat(shm,NULL,0);//記錄共享記憶體映射到虛擬記憶體上的起始地址
sleep(5);
printf("attach end\n");
shmdt(begin);//去關聯共享記憶體
sleep(1);
printf("end\n");
shmctl(shm,IPC_RMID,NULL);//歸還共享記憶體
return 0;
}
運行結果下圖:

可任意看到去關聯后掛接數減少1
④釋放共享記憶體(將資源回傳給作業系統)
命令歸還申請的共享記憶體ipcrm -m)
用命令洗掉共享記憶體不用key值洗掉,使用的是共享記憶體用戶層shmid號洗掉

呼叫(shmctl)函式(共享記憶體控制)歸還共享記憶體(sys/ipc.h -sys/shm.h)
函式原型:

引數解釋:
shmid:要歸還的共享記憶體的用戶層編號,
cmd:執行選項,IPC_RMID表示洗掉共享記憶體
回傳值:
成功回傳0,失敗回傳-1
#include"commen.h"
int main()
{
key_t key=ftok(PATH,PROJ_ID);
if(key<0)
{
perror("Error\n");
return 1;
}
int shm=shmget(key,SIZE,IPC_CREAT|IPC_EXCL);//創建一個全新的共享記憶體
if(shm<0)
{
perror("Shmget error\n");
return 2;
}
sleep(5);
shmctl(shm,IPC_RMID,NULL);//洗掉共享記憶體
return 0;
}
運行結果:

如上圖,行程退出后共享記憶體也歸還回作業系統,
3.共享記憶體的使用
共享記憶體的使用在共享記憶體掛接到行程地址空間到去關聯共享記憶體之間
通過共享記憶體掛接后回傳的映射地址來使用共享記憶體
模擬client行程與sever行程通信
思路:sevser行程創建共享記憶體后,client與sever掛接到同一塊共享記憶體上,client發送資料,sever接受資料,只要保證兩個行程看到同一個key值就可以保證兩個行程可以掛接到同一個共享記憶體上,
將client與sever行程共有代碼放到commen.h上
commen.h
#pragma once
#include<stdio.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<unistd.h>
#define PATH "/home/dodamce/SharePace"//任意路徑
#define PROJ_ID 0x666//任意值
#define SIZE 4096
sever.c 列印client發送到共享記憶體的資料
#include"commen.h"
int main()
{
key_t key = ftok(PATH, PROJ_ID);
if (key < 0)
{
perror("Error\n");
return 1;
}
int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666);//創建一個全新的共享記憶體
if (shm < 0)
{
perror("Shmget error\n");
return 2;
}
char* begin = shmat(shm, NULL, 0);
while (1)//列印共享記憶體的資料
{
printf("client:%s\n", begin);
sleep(1);
}
shmdt(begin);//去關聯共享記憶體
shmctl(shm, IPC_RMID, NULL);//歸還共享記憶體
return 0;
}
client.c
#include"commen.h"
int main()
{
key_t key = ftok(PATH, PROJ_ID);//獲取與sever一樣的key值
if (key < 0) {
perror("ftok error");
return 1;
}
int shm = shmget(key, SIZE, IPC_CREAT);//因為已經存在共享記憶體了,IPC_CREAT一定可以獲取到
if (shm < 0) {
perror("shmget error");
return 2;
}
//掛接共享記憶體
char* begin = shmat(shm, NULL, 0);
//開始通信
int size = 0;
while (1)//列印數字1,2,3...
{
begin[size] = '1' + size;
size++;
begin[size] = '\0';//為字串添加結束標志
}
shmdt(begin);//去關聯共享記憶體
return 0;
}
結果:

4.共享記憶體通信與管道通信的區別
1.建立共享記憶體后,在使用共享記憶體時沒有使用系統呼叫介面,行程掛接共享記憶體后,對共享記憶體的修改其他掛接行程立即可以收到,不用進行拷貝,
管道通信:確立完讀寫行程后,行程要呼叫write(),read()等系統介面讀寫,資料需要先拷貝到管道檔案中在通信,
共享記憶體是所有行程通信間最快的,
2.共享記憶體不提供任何保護機制,沒有互斥與同步機制,管道檔案存在互斥與同步機制
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/356875.html
標籤:其他
上一篇:低代碼開發,推薦一款Web 端自動化神器:Automa
下一篇:Linux行程信號
