通常情況下,Linux分配給兩個不同進程的內存區域既不重合,也不重疊,以防止進程之間相互干擾,從而使一個進程執行任何操作都不會影響到另一個進程的正確執行。System V IPV提供了共享內存設施,可以創建允許兩個或者多個進程間共享訪問的內存塊,為在多個進程之間共享和傳遞數據提供了一種高效的方式。如果某個進程向內存寫入數據,所做的改動將立刻被可以訪問同一段共享內存的其他任何進程看到。
基於共享內存進行通信的基本原理
在多用戶環境下,進程采用的地址是程序地址(又稱為邏輯地址、虛擬地址),程序地址划分為很多頁或者段,在指令執行過程中,由地址轉換機構將邏輯地址轉換成存儲器物理地址。一般情況下,由於系統給不同進程分配不同的存儲塊,因此可以認為,不同進程的程序地址,不管是相同還是不同,都會映射到不同的物理地址。System V IPC共享內存的基本原理是:根據進程請求分配一塊大小合適的存儲塊,各請求進程在其地址空間為該共享內存塊安排程序地址,將程序地址賦值給變量的指針,然后通過變量指針讀寫共享內存單元,從而傳輸數據。
共享內存沒有提供同步機制,因此我們通常需要用其他的機制來同步對共享內存的訪問,一般同步方法有三種:
- 通過傳遞消息來同步對共享內存的訪問,也就是通過消息通信來通知是否完成對共享內存的寫入或者讀出操作。
- 利用System V IPC提供的信號量機制。
- 在共享內存中留出一個標志單元用於同步。
在寫進程結束對共享內存的寫操作之前,並沒有自動的機制可以阻止讀進程讀取共享數據。對共享內存訪問的同步控制必須有程序員負責。
共享內存的相關API函數
共享內存的相關API函數有shmget、shmat、shmdt、shmctl四個,他們的函數聲明在頭文件sys/shm.h中。
1.shmget函數
shmget函數用於創建共享內存,該函數創建由鍵值key標識的共享內存塊,並返回標識號,如果內存塊已經存在,就直接返回其標識號。
#include <sys/shm.h> int shmget(key_t key, size_t size, int flag);
返回值:如果共享內存創建成功,shmget返回一個非負整數,即共享內存的標識號,如果失敗,則返回-1。
key:共享內存的鍵值,用於有效的對共享內存段命名,可以唯一的標識一個共享內存段,用相同的key調用shmget函數將返回同一個共享內存標識號。當key為IPC_PRIVATE時,會創建只屬於進程的私有共享內存。
size:字節為單位指定需要分享內存塊的大小。
flag:9個位的標識字段。
2.shmat函數
shmget函數返回的共享內存段還不能被進程訪問。想啟動對該共享內存的訪問,必須通過shmat函數將其映射到一個進程的邏輯地址空間,以獲得該共享內存段的程序地址。
#include <sys/shm.h> void *shmat(int shm_id, const void * shm_addr,int flag);
返回值:如果shmat調用成功,則返回一個指向共享內存中第一個字節的指針,如果失敗,則返回-1。
shm_id:shmget函數返回的共享內存標識符。
shm_addr:指定要把共享內存映射到當前進程的地址,一般設置為0,讓操作系統來安排共享內存的映射地址。
flag:一組位標志,一般設置為0。
3.shmdt函數
shmdt函數的作用是將共享內存從當前進程中分離,它的參數是shmat函數訪問的地址指針。
注意:將共享內存分離並不是刪除它,只是使該共享內存對當前進程不再可用。
#include <sys/shm.h> void *shmdt(char * shmaddr);
4.shmctl
shmctl函數用於對共享內存實施控制管理操作,原型:
#include <sys/shm.h> void *shmdt(int shm_id,int command,struct shmid_ds *buf);
shm_id:shmget函數返回的共享內存標識符。
command:要采取的動作,可以取三個值。
- IPC_STAT:獲取共享內存的shmid_ds結構並保存於buf。
- IPC_SET:使用buf的值設置共享內存shmid_ds結構。
- RMID:刪除共享內存。
buf:一個指針,指向包含共享內存模式和訪問權限的結構體。
shmid_ds結構體:
/* Data structure describing a shared memory segment. */ struct shmid_ds { struct ipc_perm shm_perm; /* operation permission struct */ size_t shm_segsz; /* size of segment in bytes */ __time_t shm_atime; /* time of last shmat() */ #ifndef __x86_64__ unsigned long int __glibc_reserved1; #endif __time_t shm_dtime; /* time of last shmdt() */ #ifndef __x86_64__ unsigned long int __glibc_reserved2; #endif __time_t shm_ctime; /* time of last change by shmctl() */ #ifndef __x86_64__ unsigned long int __glibc_reserved3; #endif __pid_t shm_cpid; /* pid of creator */ __pid_t shm_lpid; /* pid of last shmop */ shmatt_t shm_nattch; /* number of current attaches */ __syscall_ulong_t __glibc_reserved4; __syscall_ulong_t __glibc_reserved5; };
共享內存通信
shmcreate.c利用命令行參數argv[1]提供的鍵值創建或者檢索共享內存。
#include <sys/shm.h> #include <stdio.h> int main(int argc,char * argv[]){ int rtn; int msqid; key_t key; if(argc!=2){ fprintf(stderr,"請以./shmcreate <key>的形式運行,給出共享內存的鍵值!\n"); return -1; } sscanf(argv[1],"%x",&key); msqid = shmget(key,4096,IPC_CREAT|0644); return 0; }
shmwrite.c程序獲取鍵值為argv[1]的共享內存,將命令行參數argv[2]提供的信息寫入內存。
#include <sys/shm.h> #include <stdio.h> #include <string.h> int main(int argc,char * argv[]){ int rtn; int shmid; key_t key; void * shmptr; if(argc!=3){ fprintf(stderr,"請以./shmwrite <key> <msg>的形式運行,向共享內存寫入數據!\n"); return -1; } sscanf(argv[1],"%x",&key); shmid = shmget(key,4096,IPC_CREAT|0644); shmptr = shmat(shmid,0,0); memcpy(shmptr,argv[2],strlen(argv[2]) + 1); shmdt(shmptr); return 0; }
shmread.c從共享內存中讀出信息並顯示。
#include <sys/shm.h> #include <stdio.h> int main(int argc,char * argv[]){ int rtn; int shmid; key_t key; void * shmptr; if(argc!=2){ fprintf(stderr,"請以./msgcreate <key>的形式運行,讀取內存中的消息!\n"); return -1; } sscanf(argv[1],"%x",&key); shmid = shmget(key,4096,IPC_CREAT|0644); shmptr =shmat(shmid,0,0); printf("%s\n",(char *)shmptr); shmdt(shmptr); return 0; }
shmdel.c負責刪除共享內存
#include <stdio.h> #include <sys/shm.h> int main(int argc,char * argv[]){ int rtn; int shmid; key_t key; if(argc!=2){ fprintf(stderr,"請以./msgcreate <key>的形式運行,刪除共享內存!\n"); return -1; } sscanf(argv[1],"%x",&key); shmid = shmget(key,4096,IPC_CREAT|0644); shmctl(shmid,IPC_RMID,NULL); return 0; }
共享內存通信
實現一個生產者消費者應用,生產者進程shmprod.c循環讀取用戶從鍵盤輸入的信息,通過共享內存傳遞給消費者進程shmcons.c並顯示輸出,用戶輸入"end"可以結束程序的執行。
1.共享內存結構體和同步設計
為防止消費者進程從空的共享內存中讀取數據或者讀取到舊的數據,同時也防止舊的信息尚未取走就被新的信息覆蓋,生產者/消費者應用必須解決同步問題。在這里用一種比較簡單的數據標志方法來實現同步,在共享內存中留出一個單元flag作為同步標志,其他單元作為緩沖區buf使用。標志單元flag為0表示共享內存為空,flag為1表示共享內存中有新的數據,flag為2表示數據傳輸結束。初始時flag為0,生產者進程shmprod.c僅僅在flag=0時才將數據寫入內存,並將flag修改為1,若寫入結束串"end",則將flag改為2。消費者進程shmcons.c僅當flag=1時才能從共享內存讀出數據,並將flag標志修改為0。
將共享內存設計成一下結構體:
#define TEXT_SE_2048 struct shared_mm{ int flag; char buf[TEXT_SE_2048]; } * shmptr;
2.創建共享內存
程序shmcreate.c創建一個大小有要求的共享內存,並將標志位初始化為0。
#include <sys/shm.h> #include <stdint.h> #include <stdio.h> #define TEXT_SE_2048 struct shared_mm{ int flag; char buf[TEXT_SE_2048]; } * shmptr; int main(int argc,char * argv[]){ int rtn; int shmid; key_t key; if (argc!=2){ fprintf(stderr,"請以./msgcreate <key> 的形式運行,給出消息隊列的鍵值!\n"); return 0; } sscanf(argv[1],"%x",&key); shmid = shmget(key,sizeof(struct shared_mm),IPC_CREAT|0644); shmptr = (struct shared_mm *)shmat(shmid,0,0); shmptr->flag=0; return 0; }
3.設計生產者進程
程序shmprod.c從命令行參數argv[1]獲取共享內存鍵值,獲得共享內存Id,映射共享內存,然后執行循環,從標准輸入讀入數據,寫入共享內存,遇到"end"時結束循環,最后解除共享內存映射。
#include <sys/shm.h> #include <string.h> #include <stdio.h> #define TEXT_SE_2048 struct shared_mm{ int flag; char buf[TEXT_SE_2048]; } * shmptr; int main(int argc,char* argv[]){ int rtn; int shmid; key_t key; if (argc!=2){ fprintf(stderr,"請以./shmprod <key> 的形式運行,給出消息隊列的鍵值!\n"); return 0; } sscanf(argv[1],"%x",&key); shmid = shmget(key,sizeof(struct shared_mm),IPC_CREAT|0644); shmptr = (struct shared_mm *)shmat(shmid,0,0); for (;;) { while (shmptr->flag!=0); scanf("%s",shmptr->buf); if (strcmp(shmptr->buf,"end")==0){ shmptr->flag = 2; break; } else{ shmptr->flag = 1; } } shmdt(shmptr); return 0; }
4.設計消費者進程
程序shmcons.c從命令行參數argv[1]獲取共享內存鍵值,獲得共享內存Id,映射共享內存,然后執行循環,從共享內存讀出數據,輸出顯示,遇到"end"時結束循環,最后解除共享內存映射。
#include <sys/shm.h> #include <string.h> #include <stdio.h> #define TEXT_SE_2048 struct shared_mm{ int flag; char buf[TEXT_SE_2048]; } * shmptr; int main(int argc,char* argv[]){ int rtn; int shmid; key_t key; if (argc!=2){ fprintf(stderr,"請以./shmcons <key> 的形式運行,給出消息隊列的鍵值!\n"); return 0; } sscanf(argv[1],"%x",&key); shmid = shmget(key,sizeof(struct shared_mm),IPC_CREAT|0644); shmptr = (struct shared_mm *)shmat(shmid,0,0); for (;;) { while (shmptr->flag==0); printf("%s\n",shmptr->buf); shmptr->flag = 0; if (strcmp(shmptr->buf,"end")==0){ break; } } shmdt(shmptr); return 0; }