概述html
管道是OS在物理內存上開闢一段緩存空間,當進程經過read、write等API來共享讀寫這段空間時,就實現了進程間通訊。緩存
消息隊列是OS建立的鏈表,鏈表的全部節點都是保存在物理內存上的,因此消息隊列這個鏈表其實也是OS在物理內存上所開闢的緩存,當進程調用msgsnd、msgrcv等API來共享讀寫時,就實現了進程間通訊。ide
共享內存也逃不開一樣的套路。共享內存就是OS在物理內存中開闢一大段緩存空間,不過與管道、消息隊列調用read、write、msgsnd、msgrcv等API來讀寫所不一樣的是,使用共享內存通訊時,進程是直接使用地址來共享讀寫的。函數
固然無論使用那種方式,只要可以共享操做同一段緩存,就均可以實現進程間的通訊。ui
信號、管道、消息隊列、共享內存對比spa
信號:非精確通訊3d
管道:無名管道只用於親緣進程,命名管道克服了這一缺點。可是這兩種管道方式仍是不適合網狀通訊code
消息隊列:克服了無名管道只用於親緣進程,無名/命名管道 網狀通訊若的缺點。可是不能實現大規模數據的通訊。htm
共享內存:繼承了消息隊列的優勢,還克服了其缺點。支持大規模數據通訊。blog
爲啥共享內存比消息隊列效率高?
前面這4中IPC,其本質都是操做OS提供的一段虛擬內存(在引入虛擬內存機制的狀況下),虛擬內存最終仍是被OS映射到真實物理內存。信號、管道、消息隊列 都要調用各類API,在到達內存以前,通過了屢次函數調用,直到最後一個函數,該函數纔會經過地址去讀寫共享的緩存。層層調用勢必會嚴重下降效率。而共享內存就沒有這麼多麻煩,直接使用地址來讀寫內存,效率高,那是必須的!
共享內存原理
注:紫線表述有誤,共享內存不會佔據進程所有虛擬地址空間
每一個進程的虛擬內存只嚴格對應本身的那片物理內存空間,也就是說虛擬空間的虛擬地址,只和本身的那片物理內存空間的物理地址創建映射關係,和其它進程的物理內存空間沒有任何的交集,所以進程空間之間是徹底獨立的。
以兩個進程使用共享內存來通訊爲例,實現的方法就是:
(1)調用API,讓OS在物理內存上開闢出一大段緩存空間。
(2)讓各自進程空間與開闢出的緩存空間創建映射關係
創建映射關係後,每一個進程均可以經過映射後的虛擬地址來共享操做實現通訊了。
多個進程能不能映射到同一片空間,而後數據共享呢?
固然是能夠的。不過當多個進程映射並共享同一個空間時,在寫數據的時候可能會出現相互干擾,好比A進程的數據剛寫了一半沒寫完,結果切換到B進程後,B進程又開始寫,A的數據就被中間B的數據給岔開了。這時每每須要加保護措施,讓每一個進程在沒有操做時不要被別人干擾,等操做完之後,別的進程才能寫數據。
共享內存的使用步驟
①進程調用shmget函數建立新的或獲取已有共享內存。shm是share memory的縮寫。
②進程調用shmat函數,將物理內存映射到本身的進程空間。即讓虛擬地址和真實物理地址創建一 一對應的映射關係。創建映射後,就能夠直接使用虛擬地址來讀寫共享的內存空間了。
③shmdt函數,取消映射
④調用shmctl函數釋放開闢的那片物理內存空間和消息隊列的msgctl的功能是同樣的,只不過這個是共享內存的。
多個進程使用共享內存通訊時,建立者只須要一個,一樣的,通常都是誰先運行誰建立,其它後運行的進程發現已經被建立好了,就直接獲取共享使用,你們共享操做同一個內存,便可實現通訊。
API
shmget
函數原型
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
功能
建立新的,或者獲取已有的共享內存
若是key值沒有對應任何共享內存:建立一個新的共享內存,建立的過程其實就是OS在物理內存上劃出(開闢出)一段物理內存空間出來。
若是key值有對應某一個共享內存:說明以前有進程調用msgget函數,使用該key去建立了某個共享內存,既然別人以前就建立好了,那就直接獲取key所對應的共享內存。
參數
key:用於生成共享內存的標識符,使用方法參考消息隊列的key
size:指定共享內存的大小,咱們通常要求size是虛擬頁大小的整數倍。通常來講虛擬頁大小是4k(4096字節),若是你指定的大小不是虛擬頁的整數倍,也會自動幫你補成整數倍。
semflg:與消息隊列同樣。指定原始權限和IPC_CREAT,好比 0664|IPC_CREAT。只有在建立一個新的共享內存時纔會用到,否者不會用到。
返回值
成功:返回共享內存的標識符,之後續操做
失敗:返回-1,而且errno被設置。
shmat
函數原型
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg);
功能
將shmid所指向的共享內存空間映射到進程空間(虛擬內存空間),並返回影射後的起始地址(虛擬地址)。有了這個地址後,就能夠經過這個地址對共享內存進行讀寫操做。
參數
shmid:共享內存標識符。
shmaddr:指定映射的起始地址,有兩種設置方式
①本身指定映射的起始地址(虛擬地址)。咱們通常不會這麼作,由於咱們本身都搞不清哪些虛擬地址被用了,哪些沒被用。
②NULL:表示由內核本身來選擇映射的起始地址(虛擬地址)。這是最多見的方式,也是最合理的方式,由於只有內核本身才知道哪些虛擬地址可用,哪些不可用。
shmflg:指定映射條件。
0:以可讀可寫的方式映射共享內存,也就是說映射後,能夠讀、也能夠寫共享內存。
SHM_RDONLY:以只讀方式映射共享內存,也就是說映射後,只能讀共享內存,不能寫。
返回值
成功:則返回映射地址
失敗:返回(void *)-1,而且errno被設置。
shmdt
函數原型
#include <sys/types.h> #include <sys/shm.h> int shmdt(const void *shmaddr);
功能
取消創建的映射。
參數
shmaddr:映射的起始地址(虛擬地址)。
返回值
調用成功返回0,失敗返回-1,且errno被設置。
shmctl
函數原型
#include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能
根據cmd的要求,對共享內存進行相應控制。
好比:
獲取共享內存的屬性信息
修改共享內存的屬性信息
刪除共享內存
等等
刪除共享內存是最多見的控制。
參數
shmid:標識符。
cmd:控制選項
①IPC_STAT:從內核獲取共享內存屬性信息到第三個參數(應用緩存)。
②IPC_SET:修改共享內存的屬性。修改方法與消息隊列相同。
③IPC_RMID:刪除共享內存,不過前提是只有當全部的映射取消後,才能刪除共享內存。刪除時,用不着第三個參數,因此設置爲NULL
buf:buf的類型爲struct shmid_ds。
①cmd爲IPC_STAT時,buf用於存儲原有的共享內存屬性,以供查看。
②cmd爲IPC_SET時,buf中放的是新的屬性設置,用於修改共享內存的屬性。
struct shmid_ds結構體
struct shmid_ds { struct ipc_perm shm_perm; /* Ownership and permissions:權限 */ size_t shm_segsz; /* Size of segment (bytes):共享內存大小 */ time_t shm_atime; /* Last attach time:最後一次映射的時間 */ time_t shm_dtime; /* Last detach time:最後一次取消映射的時間 */ time_t shm_ctime; /* Last change time:最後一次修改屬性信息的時間 */ pid_t shm_cpid; /* PID of creator:建立進程的PID */ pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) :當前正在使用進程的PID*/ shmatt_t shm_nattch; /* No. of current attaches:映射數量,標記有多少個進程空間映射到了共享內存上,每增長一個映射就+1,每取消一個映射就-1 */ ... };
struct ipc_perm,這個結構體咱們在講消息隊列時已經講過
struct ipc_perm { key_t __key; /* Key supplied to shmget(2) */ uid_t uid; /* UID of owner */ gid_t gid; /* GID of owner */ uid_t cuid; /* UID of creator */ gid_t cgid; /* GID of creator */ unsigned short mode; /* Permissions + SHM_DEST andSHM_LOCKED flags */ unsigned short __seq; /* Sequence number */ };
返回值
調用成功0,失敗則返回-1,而且errno被設置。
共享內存示例代碼
單向通行
p1.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/types.h> #include <sys/shm.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <strings.h> #include <string.h> #include <signal.h> #include <errno.h> #define SHM_FILE "./shmfile" #define SHM_SIZE 4096 int shmid = -1; char buf[300] = {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ 222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222\ ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2222222222"}; void print_err(char *estr) { perror(estr); exit(-1); } void create_or_get_shm(void) { int fd = 0; key_t key = -1; fd = open(SHM_FILE, O_RDWR|O_CREAT, 0664); if(fd == -1) print_err("open fail"); key = ftok(SHM_FILE, 'b'); if(key == -1) print_err("ftok fail"); shmid = shmget(key, SHM_SIZE, 0664|IPC_CREAT); if(shmid == -1) print_err("shmget fail"); //read(fd, &shmid, sizeof(shmid)); } int main(void) { void *shmaddr = NULL; /* 建立、或者獲取共享內存 */ create_or_get_shm(); //創建映射 shmaddr = shmat(shmid, NULL, 0); if(shmaddr == (void *)-1) print_err("shmat fail"); while(1) { memcpy(shmaddr, buf, sizeof(buf)); sleep(1); } return 0; }
p2.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/types.h> #include <sys/shm.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <strings.h> #include <string.h> #include <signal.h> #include <errno.h> #define SHM_FILE "./shmfile" #define SHM_SIZE 4096 int shmid = -1; void print_err(char *estr) { perror(estr); exit(-1); } void create_or_get_shm(void) { int fd = 0; key_t key = -1; fd = open(SHM_FILE, O_RDWR|O_CREAT, 0664); if(fd == -1) print_err("open fail"); key = ftok(SHM_FILE, 'b'); if(key == -1) print_err("ftok fail"); shmid = shmget(key, SHM_SIZE, 0664|IPC_CREAT); if(shmid == -1) print_err("shmget fail"); //read(fd, &shmid, sizeof(shmid)); } int main(void) { void *shmaddr = NULL; /* 建立、或者獲取共享內存 */ create_or_get_shm(); //創建映射 shmaddr = shmat(shmid, NULL, 0); if(shmaddr == (void *)-1) print_err("shmat fail"); while(1) { if(strlen((char*)shmaddr)!=0) { printf("%s\n", (char *)shmaddr); bzero(shmaddr, SHM_SIZE); } } return 0; }
這種寫法,在Ctrl+C硬件中斷進程後,並不會刪除共享內存。下面代碼改進這一問題
p1.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/types.h> #include <sys/shm.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <strings.h> #include <string.h> #include <signal.h> #include <errno.h> #define SHM_FILE "./shmfile" #define SHM_SIZE 4096 int shmid = -1; void *shmaddr = NULL; void print_err(char *estr) { perror(estr); exit(-1); } void create_or_get_shm(void) { int fd = 0; key_t key = -1; fd = open(SHM_FILE, O_RDWR|O_CREAT, 0664); if(fd == -1) print_err("open fail"); key = ftok(SHM_FILE, 'b'); if(key == -1) print_err("ftok fail"); shmid = shmget(key, SHM_SIZE, 0664|IPC_CREAT); if(shmid == -1) print_err("shmget fail"); //write(fd, &shmid, sizeof(shmid)); } char buf[300] = {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ 222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222\ ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2222222222"}; void signal_fun(int signo) { shmdt(shmaddr); shmctl(shmid, IPC_RMID, NULL); remove("./fifo"); remove(SHM_FILE); exit(-1); } int get_peer_PID(void) { int ret = -1; int fifofd = -1; /* 建立有名管道文件 */ ret = mkfifo("./fifo", 0664); if(ret == -1 && errno != EEXIST) print_err("mkfifo fail"); /* 以只讀方式打開管道 */ fifofd = open("./fifo", O_RDONLY); if(fifofd == -1) print_err("open fifo fail"); /* 讀管道,獲取「讀共享內存進程」的PID */ int peer_pid; ret = read(fifofd, &peer_pid, sizeof(peer_pid)); if(ret == -1) print_err("read fifo fail"); return peer_pid; } int main(void) { int peer_pid = -1; /* 給SIGINT信號註冊捕獲函數,用於刪除共享內存、管道、文件等 */ signal(SIGINT, signal_fun); /* 使用有名管道獲取讀共享內存進程的PID */ peer_pid = get_peer_PID(); /* 建立、或者獲取共享內存 */ create_or_get_shm(); //創建映射 shmaddr = shmat(shmid, NULL, 0); if(shmaddr == (void *)-1) print_err("shmat fail"); while(1) { memcpy(shmaddr, buf, sizeof(buf)); kill(peer_pid, SIGUSR1); sleep(1); } return 0; }
p2.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/types.h> #include <sys/shm.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <strings.h> #include <string.h> #include <signal.h> #include <errno.h> #define SHM_FILE "./shmfile" #define SHM_SIZE 4096 int shmid = -1; void *shmaddr = NULL; void print_err(char *estr) { perror(estr); exit(-1); } void create_or_get_shm(void) { int fd = 0; key_t key = -1; fd = open(SHM_FILE, O_RDWR|O_CREAT, 0664); if(fd == -1) print_err("open fail"); key = ftok(SHM_FILE, 'b'); if(key == -1) print_err("ftok fail"); shmid = shmget(key, SHM_SIZE, 0664|IPC_CREAT); if(shmid == -1) print_err("shmget fail"); //read(fd, &shmid, sizeof(shmid)); } void signal_fun(int signo) { if(SIGINT == signo) { shmdt(shmaddr); shmctl(shmid, IPC_RMID, NULL); remove("./fifo"); remove(SHM_FILE); exit(-1); } else if(SIGUSR1 == signo) { } } void snd_self_PID(void) { int ret = -1; int fifofd = -1; /* 建立有名管道文件 */ mkfifo("./fifo", 0664); if(ret == -1 && errno != EEXIST) print_err("mkfifo fail"); /* 以只寫方式打開文件 */ fifofd = open("./fifo", O_WRONLY); if(fifofd == -1) print_err("open fifo fail"); /* 獲取當前進程的PID, 使用有名管道發送給寫共享內存的進程 */ int pid = getpid(); ret = write(fifofd, &pid, sizeof(pid));//發送PID if(ret == -1) print_err("write fifo fail"); } int main(void) { /*給SIGUSR1註冊一個空捕獲函數,用於喚醒pause()函數 */ signal(SIGUSR1, signal_fun); signal(SIGINT, signal_fun); /* 使用有名管道,講當前進程的PID發送給寫共享內存的進程 */ snd_self_PID(); /* 建立、或者獲取共享內存 */ create_or_get_shm(); //創建映射 shmaddr = shmat(shmid, NULL, 0); if(shmaddr == (void *)-1) print_err("shmat fail"); while(1) { pause(); printf("%s\n", (char *)shmaddr); bzero(shmaddr, SHM_SIZE); } return 0; }
雙向通訊