進程間通信IPC之--共享內存

每個進程各自有不同的用戶地址空間,任何一個進 程的全局變量在另一個進程中都看不到,所以進程之間要交換數據必須通過內核,在內核中開闢一塊緩衝 區,進程1把數據從用戶空間拷到內核緩衝區,進程2再從內核緩衝區把數據讀走,內核提供的這種機制稱爲進程間通信(IPC,InterProcess Communication)

如下圖所示:
進程間通信共七種方式:
第一類:傳統的unix通信機制:
# 管道( pipe ):管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關係的進程間使用。進程的親緣關係通常是指父子進程關係。
# 有名管道 (named pipe) : 有名管道也是半雙工的通信方式,但是它允許無親緣關係進程間的通信。
# 信號 ( sinal ) : 信號是一種比較複雜的通信方式,用於通知接收進程某個事件已經發生。

第二類:System V IPC: 
# 信號量( semophore ) : 信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作爲一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作爲進程間以及同一進程內不同線程之間的同步手段。
# 消息隊列( message queue ) : 消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點。
# 共享內存( shared memory ) :共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號兩,配合使用,來實現進程間的同步和通信。

第三類:BSD 套接字:
# 套接字( socket ) : 套解字也是一種進程間通信機制,與其他通信機制不同的是,它可用於不同及其間的進程通信。

本文介紹IPC之共享內存:
共享內存允許兩個或更多進程訪問同一塊內存,就如同 malloc() 函數向不同進程返回了指向同一個物理內存區域的指針。當一個進程改變了這塊地址中的內容的時候,其它進程都會察覺到這個更改進程可以直接讀取共享內存,不需要拷貝數據。由於多個進程共享同一塊內存區域,必然需要某種同步機制,互斥鎖和信號量都可以。
步驟:
共享內存的使用,主要有以下幾個API:ftok()、shmget()、shmat()、shmdt()及shmctl()
①創建/打開共享內存
int shmget(key_t key,int size,int shmflag);
例:
key = ftok(".", 'm');
shmid = shmget(key,1024,0666|IPC_CREAT|IPC_EXCL);
參數說明:
key:是這塊共享內存的標識符。如果是父子關係的進程間通信的話,這個標識符用IPC_PRIVATE來代替。如果兩個進程沒有任何關係,所以就用ftok()算出來一個標識符(或者自己定義一個)使用了。
產生key的方法:key_t ftok(const char *pathname, int proj_id);
1)pathname一定要在系統中存在並且進程能夠訪問的
2)proj_id是一個1-255之間的一個整數值,典型的值是一個ASCII值。如'a',考慮到應用系統可能在不同的主機上應用,可以直接定義一個key,而不用ftok獲得:
#define IPCKEY 0x344378
size:申請內存的大小
shmflag:  這塊內存的模式(mode)以及權限標識(0666等)。 
 模式可取如下值:        
IPC_CREAT 新建(如果已創建則返回目前共享內存的id)
IPC_EXCL   與IPC_CREAT結合使用,如果已創建則則返回錯誤 
 其他模式略
申請的內存裏面爲空,即全爲0
②映射共享內存,即把指定的共享內存映射到進程的地址空間用於訪問
void *shmat( int shmid , char *shmaddr , int shmflag );
例:
void *p;
p=shmat(shmid,NULL,0);
參數說明:
shmid:是那塊共享內存的ID。
shmaddr:共享內存映射的起始地址,一般不指定即NULL
shmflag:本進程對該內存的操作模式。如果是SHM_RDONLY的話,就是隻讀模式。0是讀寫模式
返回值: 成功時,這個函數返回共享內存的起始地址。失敗時返回-1。
③撤銷共享內存映射,刪除本進程對這塊內存的使用
int shmdt(const void* shmaddr);
例:
shmdt(p);
參數說明:
shmaddr:共享內存的起始地址。
返回值:成功時返回0。失敗時返回-1。
④刪除共享內存對象
int shmctl( int shmid , int cmd , struct shmid_ds *buf );
例:
shmctl(shmid, IPC_RMID, NULL);(刪除共享內存)
參數說明:
shmid:共享內存的ID。
cmd:控制命令,可取值如下:
        IPC_STAT        得到共享內存的狀態
        IPC_SET         改變共享內存的狀態
        IPC_RMID        刪除共享內存
buf:一個結構體指針。用以保存/設置屬性。IPC_STAT的時候,取得的狀態放在這個結構體中。如果要改變共享內存的狀態,用這個結構體指定。
返回值:成功時返回0。失敗時返回-1。
無代碼無真相

功能說明:兩個進程通過共享內存一個寫一個讀並打印數據
  1. /*name:writer.c
  2.  *function:寫端進程向共享內存寫數據
  3.  * */
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <unistd.h>
  7. #include <errno.h>
  8. #include <signal.h>
  9. #include <string.h>
  10. #include <sys/types.h>
  11. #include <sys/ipc.h>
  12. #include <sys/shm.h>

  13. #define N 64

  14. typedef struct 
  15. {
  16.     pid_t pid;
  17.     char buf[N];
  18. } SHM;

  19. void handler(int signo)
  20. {
  21.     //printf("get signal\n");
  22.     return;
  23. }

  24. int main()
  25. {
  26.     key_t key;
  27.     int shmid;
  28.     SHM *p;
  29.     pid_t pid;

  30.     if ((key = ftok(".", 'm')) < 0)
  31.     {
  32.         perror("fail to ftok");
  33.         exit(-1);
  34.     }

  35.     signal(SIGUSR1, handler);//註冊一個信號處理函數
  36.     if ((shmid = shmget(key, sizeof(SHM), 0666|IPC_CREAT|IPC_EXCL)) < 0)
  37.     {
  38.         if (EEXIST == errno)//存在則直接打開
  39.         {
  40.             shmid = shmget(key, sizeof(SHM), 0666);
  41.             p = (SHM *)shmat(shmid, NULL, 0);
  42.             pid = p->pid;
  43.             p->pid = getpid();
  44.             kill(pid, SIGUSR1);
  45.         }
  46.         else//出錯
  47.         {
  48.             perror("fail to shmget");
  49.             exit(-1);
  50.         }
  51.     }
  52.     else//成功 
  53.     {
  54.         p = (SHM *)shmat(shmid, NULL, 0);
  55.         p->pid = getpid();//把自己的pid寫到共享內存
  56.         pause();
  57.         pid = p->pid;//得到讀端進程的pid
  58.     }

  59.     while ( 1 )
  60.     {
  61.         printf("write to shm : ");
  62.         fgets(p->buf, N, stdin);//接收輸入
  63.         kill(pid, SIGUSR1);//向讀進程發SIGUSR1信號
  64.         if (strcmp(p->buf, "quit\n") == 0) break;
  65.         pause();//阻塞,等待信號
  66.     }
  67.     shmdt(p);
  68.     shmctl(shmid, IPC_RMID, NULL);//刪除共享內存

  69.     return 0;
  70. }
  1. /*name:reader.c
  2.  *function:讀端進程從共享內存中讀數據
  3.  * */
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <unistd.h>
  7. #include <errno.h>
  8. #include <signal.h>
  9. #include <string.h>
  10. #include <sys/types.h>
  11. #include <sys/ipc.h>
  12. #include <sys/shm.h>

  13. #define N 64

  14. typedef struct 
  15. {
  16.     pid_t pid;
  17.     char buf[N];
  18. } SHM;

  19. void handler(int signo)
  20. {
  21.     //printf("get signal\n");
  22.     return;
  23. }

  24. int main()
  25. {
  26.     key_t key;
  27.     int shmid;
  28.     SHM *p;
  29.     pid_t pid;

  30.     if ((key = ftok(".", 'm')) < 0)
  31.     {
  32.         perror("fail to ftok");
  33.         exit(-1);
  34.     }

  35.     signal(SIGUSR1, handler);//註冊一個信號處理函數
  36.     if ((shmid = shmget(key, sizeof(SHM), 0666|IPC_CREAT|IPC_EXCL)) < 0)
  37.     {
  38.         if (EEXIST == errno)//存在則直接打開
  39.         {
  40.             shmid = shmget(key, sizeof(SHM), 0666);
  41.             p = (SHM *)shmat(shmid, NULL, 0);
  42.             pid = p->pid;
  43.             p->pid = getpid();//把自己的pid寫到共享內存
  44.             kill(pid, SIGUSR1);
  45.         }
  46.         else//出錯
  47.         {
  48.             perror("fail to shmget");
  49.             exit(-1);
  50.         }
  51.     }
  52.     else//成功
  53.     {
  54.         p = (SHM *)shmat(shmid, NULL, 0);
  55.         p->pid = getpid();
  56.         pause();
  57.         pid = p->pid;//得到寫端進程的pid
  58.     }

  59.     while ( 1 )
  60.     {
  61.         pause();//阻塞,等待信號
  62.         if (strcmp(p->buf, "quit\n") == 0) exit(0);//輸入"quit結束"
  63.         printf("read from shm : %s", p->buf);
  64.         kill(pid, SIGUSR1);//向寫進程發SIGUSR1信號
  65.     }

  66.     return 0;
  67. }
發佈了73 篇原創文章 · 獲贊 23 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章