目錄
一.共享記憶體實作程序間通信的原理
二.管理共享記憶體的資料結構
三.共享記憶體函數
四.實作程序間通信
接部落格:程序間通信之管道
一.共享記憶體實作程序間通信的原理
共享記憶體實際是作業系統在實際實體記憶體中開辟的一段記憶體。
共享記憶體實作程序間通信,是作業系統在實際實體記憶體開辟一塊空間,一個程序在自己的頁表中,将該空間和程序位址空間上的共享區的一塊位址空間形成映射關系。另外一程序在頁表上,将同一塊實體空間和該程序位址空間上的共享區的一塊位址空間形成映射關系。
當一個程序往該空間寫入内容時,另外一程序通路該空間,會得到寫入的值,即實作了程序間的通信。
要實作程序間通信需要兩個程序能夠看到同一塊空間,系統開辟的共享記憶體就是兩程序看到的同一資源。
注意:共享記憶體實作程序間通信是程序間通信最快的。
關于頁表的補充:
程序位址空間裡有一個核心區域,它們也會在實際實體記憶體開辟空間,也會有頁表與那塊空間形成映射關系,這個頁表叫做核心級頁表。因為核心隻有一個,是以每個程序都相同的。說明程序都共用實際實體記憶體上的核心空間。
除核心空間以外的空間,與實際實體空間之間的頁表,稱為使用者級頁表。每個程序可能不同。
二.管理共享記憶體的資料結構
共享記憶體實作程序間通信不隻僅限于兩個程序之間,可以用于多個程序之間。并且系統中可能會有多個程序在進行多個通信。是以系統需要将這些通信的程序管理起來。如果不管理,作業系統怎麼知道這塊共享記憶體挂接了哪個程序等資訊。
如何管理?先描述群組織。
檢視核心源代碼,可以看到系統描述共享記憶體的資料結構如下:
/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) *///共享記憶體空間大小
__kernel_time_t shm_atime; /* last attach time *///挂接時間
__kernel_time_t shm_dtime; /* last detach time *///取消挂接時間
__kernel_time_t shm_ctime; /* last change time *///改變時間
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches *///程序挂接數
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
描述共享記憶體的資料結構裡儲存了一個ipc_perm結構體,這個結構體儲存了IPC(程序将通信)的關鍵資訊。
/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct ipc_perm
{
__kernel_key_t key;//共享記憶體的唯一辨別符
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode; //權限
unsigned short seq;
};
其中的key是共享記憶體的唯一辨別。
三.共享記憶體函數
- ftok函數
作用:算出一個唯一的key傳回。
參數:第一個是位址,第二個是至少8為的項目id,不能為0,兩參數可以是任意值,但是要符合格式。
傳回值:ftok如果成功傳回一個key值,如果失敗傳回-1。如果失敗了再重新填寫參數即可。
ftok中的參數可以随便填寫,但是要符合格式,ftok隻是利用參數,再運用一套算法,算出一個唯一的key值傳回。這個key值可以傳給共享記憶體參數,作為struct ipc_perm中唯一辨別共享記憶體的key。
ftok函數并沒有涉及核心層面。
#pragma once
#define PATHNAME "./"
#define PROJ_ID 0x666
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include"com.h"
int main(){
key_t k = ftok(PATHNAME,PROJ_ID);
if(k==-1){
perror("ftok error");
return 1;
}
printf("ftok : %d\n",k);
return 0;
}
- shmget函數
作用:建立一個共享記憶體
參數:
key:為共享記憶體的名字,一般是ftok的傳回值。
size:共享記憶體的大小,以page為機關,大小為4096的整數倍。
shmflg:權限标志,常用兩個IPC_CREAT和IPC_EXCL,一般後面還加一個權限,相當于檔案的權限。
IPC_CREAT:建立一個共享記憶體傳回,已存在打開傳回
IPC_EXCL:配合着IPC_CREAT使用,共享記憶體已存在出錯傳回。
使用:IPC_CREAT | IPC_EXCL | 0666
傳回值:
成功傳回一個非負整數,即共享記憶體的辨別碼,失敗傳回-1。
為什麼已經有一個key來辨別共享記憶體,還需要一個傳回值來辨別共享記憶體?因為key是核心級别的,供核心辨別,shmget傳回值是使用者級别的,供使用者使用的。
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include"com.h"
int main(){
key_t k = ftok(PATHNAME, PROJ_ID);
if (k == -1){
perror("ftok error");
return 1;
}
printf("ftok : %d\n", k);
int shmid = shmget(k, SIZE, IPC_CREAT | IPC_EXCL | 0666);
if (shmid == -1){
perror("shmget error");
return 1;
}
printf("shmid : %d\n", shmid);
return 0;
}
我們發現當程序建立了一個共享記憶體,沒有釋放,程序結束後,共享記憶體還在,是以第二次執行程式,會報錯(報錯是因為IPC_EXCL)。
這裡得出一個結論:IPC(程序将通信)資源生命周期不随程序,而是随核心的,不釋放會一直占用,除非重新開機。是以,shmget建立的共享記憶體要釋放掉,不然會記憶體洩漏。
可以用指令行來釋放共享記憶體:ipcrm -m shmid(shmget傳回值)
也可以用下面的函數來釋放共享記憶體
- shmctl函數
作用:用于控制共享記憶體
參數: shmid:共享記憶體的辨別
cmd:以什麼方式來控制共享記憶體。IPC_RMID是釋放共享記憶體
buf:指向一個共享記憶體的資料結構 。struct shmid_ds
傳回值:成功傳回0,失敗傳回-1。
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<sys/ipc.h>
4 #include<sys/shm.h>
5 #include"com.h"
6
7 int main(){
8 key_t k = ftok(PATHNAME,PROJ_ID);//擷取一個唯一辨別符key
9 if(k==-1){
10 perror("ftok error");
11 return 1;
12 }
13
14 printf("ftok : %d\n",k);
15
16 int shmid = shmget(k,SIZE,IPC_CREAT | IPC_EXCL | 0666);//建立共享記憶體
17 if(shmid == -1){
18 perror("shmget error");
19 return 1;
20 }
21 printf("shmid : %d\n",shmid);
22
23 int sh = shmctl(shmid,IPC_RMID,NULL);//删除共享記憶體
24 if(sh == -1){
25 perror("shmctl");
26 return 1;
27 }
28 return 0;
29 }
現在就可以執行多次:
- shmat函數
作用:使建立的共享記憶體與調用該函數程序的程序位址空間參數關聯。
參數:
shmid:共享記憶體的辨別,shmget的傳回值。
shmaddr:指定程序位址空間連接配接的位址。如果設定為null,預設讓系統定要關聯的位址。
shmflg: 權限,常見有兩個SHM_RDONLY(隻讀)和SHM_REMAP(重新映射一個程序位址空間沒這樣shmaddr不能為空)。設為0,系統預設。
傳回值:
傳回映射到程序位址空間共享區的開始位址。
- shmdt函數
作用:删除共享記憶體與程序位址空間的映射關系,将頁表映射關系删除,釋放程序位址空間。
參數:
shmaddr:共享記憶體映射到程序位址空間的位址。shmat傳回值。
傳回值:
成功傳回0,失敗傳回-1
shmat和shmdt要一起使用才起作用。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/ipc.h>
5 #include<sys/shm.h>
6 #include"com.h"
7
8 int main(){
9 key_t k = ftok(PATHNAME,PROJ_ID);
10 if(k==-1){
11 perror("ftok error");
12 return 1;
13 }
14
15 printf("ftok : %d\n",k);
16 int shmid = shmget(k,SIZE,IPC_CREAT | IPC_EXCL | 0666);
17 if(shmid == -1){
18 perror("shmget error");
19 return 1;
20 }
21 printf("shmid : %d\n",shmid);
22 //與程序位址空間産生關聯
23 char *str = (char *)shmat(shmid, NULL, 0);
24 //10秒後删除關聯
25 sleep(10);
26 shmdt(str);
27
28 //int sh = shmctl(shmid,IPC_RMID,NULL);
29 //if(sh == -1){
31 // return 1;
32 //}
33 return 0;
34 }
程式10秒鐘删除共享記憶體與程序位址空間的關聯:
四.實作程序間通信
實作程序間通信步驟:
- 建立共享記憶體
- 共享記憶體關聯程序
- 删除共享記憶體與程序的關聯
- 釋放共享記憶體
使用到上面的函數。
實作一個使用者client與伺服器server之間的簡單通信。
需要client和server都和共享記憶體關聯。client端不建立共享記憶體,不釋放共享記憶體,server端建立共享記憶體,并且釋放共享記憶體。
1 #pragma once
2
3 #define PATHNAME "./"
4 #define PROJ_ID 0x666
5
6 #define SIZE 4096
client代碼:寫入端
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/ipc.h>
5 #include<sys/shm.h>
6 #include"com.h"
7
8 int main(){
9 key_t k = ftok(PATHNAME,PROJ_ID);
10 if(k==-1){
11 perror("ftok error");
12 return 1;
13 }
14 //不建立共享記憶體,隻是為了得到shmid
15 int shmid = shmget(k, SIZE, 0);
16 if(shmid == -1){
17 perror("shmget error");
18 return 1;
19 }
20 //與程序位址空間産生關聯
21 char *str = (char *)shmat(shmid, NULL, 0);
22 char c='a';
23 for(;c<='z';c++){
24 str[c-'a']=c;
25 sleep(5);
26 }
27 //删除關聯
28 shmdt(str);
29 //不用釋放共享記憶體,伺服器端釋放
30
31 return 0;
32 }
server代碼:讀出端
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/ipc.h>
5 #include<sys/shm.h>
6 #include"com.h"
7
8 int main(){
9 key_t k = ftok(PATHNAME,PROJ_ID);
10 if(k==-1){
11 perror("ftok error");
12 return 1;
13 }
14 //伺服器關不要加IPC_EXCL,已存在不要儲存,直接傳回
15 int shmid = shmget(k,SIZE,IPC_CREAT | IPC_EXCL | 0666);
16 if(shmid == -1){
17 perror("shmget error");
18 return 1;
19 }
20 //與程序位址空間産生關聯
21 char *str = (char *)shmat(shmid, NULL, 0);
22 //讀資料
23 while(1){
24 printf("%s\n",str);
25 sleep(1);
26 }
27 //删除關聯
28 shmdt(str);
29 int sh = shmctl(shmid,IPC_RMID,NULL);
30 if(sh == -1){
31 perror("shmctl");
32 return 1;
33 }
34 return 0;
35 }
代碼輸出,每隔5秒寫端往共享記憶體中寫一個字元,讀端每個一秒讀一下資料。
這裡有一個問題:因為寫端比讀端慢,相比較于管道,當寫端比讀端慢時,沒有寫入時,讀端要進入阻塞狀态,但是上面發現使用共享記憶體的讀端一直在讀,并沒有阻塞。
這裡得出一個結論:共享記憶體實作的程序間通信底層不提供任何同步與互斥機制。如果想讓兩程序很好的合作起來,在IPC裡要有信号量來支撐。
注意:兩程序之間用通過同樣的規則即ftok參數要一樣,來擷取同樣的key值,然後在建立共享記憶體時,用這個key值唯一辨別共享記憶體,是以兩程序時通過同樣的key值來實作看到同一份資源。
補充: