linux進程間通信-共享內存


一 共享內存介紹

     共享內存可以從字面上去理解,就把一片邏輯內存共享出來,讓不同的進程去訪問它,修改它。共享內存是在兩個正在運行的進程之間共享和傳遞數據的一種非常有效的方式。不同進程之間共享的內存通常安排為同一段物理內存。進程可以將同一段共享內存連接到它們自己的地址空間中,所有進程都可以訪問共享內存中的地址,就好像它們是由用C語言函數malloc分配的內存一樣。而如果某個進程向共享內存寫入數據,所做的改動將立即影響到可以訪問同一段共享內存的任何其他進程。

但有一點特別要注意:共享內存並未提供同步機制。也就是說,在第一個進程結束對共享內存的寫操作之前,並無自動機制可以阻止第二個進程開始對它進行讀取。所以我們通常需要用其他的機制來同步對共享內存的訪問,例如信號量。

二 共享內存的使用

 創建共享內存

 int shmget(key_t key, size_t size, int shmflg);

第一個參數,共享內存段命名,shmget函數成功時返回一個與key相關的共享內存標識符(非負整數),用於后續的共享內存函數。調用失敗返回-1.

    其它的進程可以通過該函數的返回值訪問同一共享內存,它代表進程可能要使用的某個資源,程序對所有共享內存的訪問都是間接的,程序先通過調用shmget函數並提供一個鍵,再由系統生成一個相應的共享內存標識符(shmget函數的返回值),只有shmget函數才直接使用信號量鍵,所有其他的信號量函數使用由semget函數返回的信號量標識符。

第二個參數,size以字節為單位指定需要共享的內存容量

第三個參數,shmflg是權限標志,它的作用與open函數的mode參數一樣,如果要想在key標識的共享內存不存在時,創建它的話,可以與IPC_CREAT做或操作。共享內存的權限標志與文件的讀寫權限一樣,舉例來說,0644,它表示允許一個進程創建的共享內存被內存創建者所擁有的進程向共享內存讀取和寫入數據,同時其他用戶創建的進程只能讀取共享內存。

 ◆啟動對該共享內存的訪問

void *shmat(int shm_id, const void *shm_addr, int shmflg);

第一次創建完共享內存時,它還不能被任何進程訪問,shmat函數的作用就是用來啟動對該共享內存的訪問,並把共享內存連接到當前進程的地址空間

第一個參數,shm_id是由shmget函數返回的共享內存標識。

第二個參數,shm_addr指定共享內存連接到當前進程中的地址位置,通常為空,表示讓系統來選擇共享內存的地址。

第三個參數,shm_flg是一組標志位,通常為0

調用成功時返回一個指向共享內存第一個字節的指針,如果調用失敗返回-1.

 ◆將共享內存從當前進程中分離

int shmdt(const void *shmaddr);

該函數用於將共享內存從當前進程中分離。注意,將共享內存分離並不是刪除它,只是使該共享內存對當前進程不再可用

參數shmaddrshmat函數返回的地址指針,調用成功時返回0,失敗時返回-1

 ◆控制共享內存

int shmctl(int shm_id, int command, struct shmid_ds *buf);

第一個參數,shm_idshmget函數返回的共享內存標識符。

第二個參數,command是要采取的操作,它可以取下面的三個值 :

    IPC_STAT:把shmid_ds結構中的數據設置為共享內存的當前關聯值,即用共享內存的當前關聯值覆蓋shmid_ds的值。

    IPC_SET:如果進程有足夠的權限,就把共享內存的當前關聯值設置為shmid_ds結構中給出的值

    IPC_RMID:刪除共享內存段

第三個參數,buf是一個結構指針,它指向共享內存模式和訪問權限的結構。

 

  1. struct shmid_ds
  2. {
  3. uid_t shm_perm.uid;
  4. uid_t shm_perm.gid;
  5. mode_t shm_perm.mode;

 

三 例子

shmdata.h的源碼:

 

  1. #ifndef _SHMDATA_H_HEADER
  2. #define _SHMDATA_H_HEADER
  3. #define TEXT_SZ 2048
  4. struct shared_use_st
  5. {
  6. int written;/* 作為一個標志,非0:表示可讀,0表示可寫 */
  7. char text[TEXT_SZ];/* 記錄寫入和讀取的文本 */
  8. };
  9. #endif

 

shmread.c的源代碼

 

  1. #include<unistd.h>
  2. #include<stdlib.h>
  3. #include<stdio.h>
  4. #include<sys/shm.h>
  5. #include"shmdata.h"
  1.  
  2. #define MEM_KEY (1234)
  3.  
  4. int main()
  5. {
  6. int running =1; //程序是否繼續運行的標志
  7. void*shm = NULL; //分配的共享內存的原始首地址
  8. struct shared_use_st *shared;//指向shm
  9. int shmid; //共享內存標識符
  10. //創建共享內存
  11.  
  12. shmid = shmget((key_t)MEM_KEY,sizeof(struct shared_use_st),0666|IPC_CREAT);
  13. if(shmid ==-1)
  14. {
  15. fprintf(stderr,"shmget failed\n");
  16. exit(EXIT_FAILURE);
  17. }
  18. //將共享內存連接到當前進程的地址空間
  19. shm = shmat(shmid,0,0);
  20. if(shm ==(void*)-1)
  21. {
  22. fprintf(stderr,"shmat failed\n");
  23. exit(EXIT_FAILURE);
  24. }
  25. printf("\nMemory attached at %X\n",(int)shm);
  26. //設置共享內存
  27. shared =(struct shared_use_st*)shm;
  28. shared->written =0;
  29. while(running)//讀取共享內存中的數據
  30. {
  31. //沒有進程向共享內存定數據有數據可讀取
  32. if(shared->written !=0)
  33. {
  34. printf("You wrote: %s", shared->text);
  35. sleep(rand()%3);
  36. //讀取完數據,設置written使共享內存段可寫
  37. shared->written =0;
  38. //輸入了end,退出循環(程序)
  39. if(strncmp(shared->text,"end",3)==0)
  40. running =0;
  41. }
  42. else//有其他進程在寫數據,不能讀取數據
  43. sleep(1);
  44. }
  45. //把共享內存從當前進程中分離
  46. if(shmdt(shm)==-1)
  47. {
  48. fprintf(stderr,"shmdt failed\n");
  49. exit(EXIT_FAILURE);
  50. }
  51. //刪除共享內存
  52. if(shmctl(shmid, IPC_RMID,0)==-1)
  53. {
  54. fprintf(stderr,"shmctl(IPC_RMID) failed\n");
  55. exit(EXIT_FAILURE);
  56. }
  57. exit(EXIT_SUCCESS);
  58. }

 

shmwrite.c的源代碼

 

  1. #include<unistd.h>
  2. #include<stdlib.h>
  3. #include<stdio.h>
  4. #include<string.h>
  5. #include<sys/shm.h>
  6. #include"shmdata.h"
  7. #define MEM_KEY (1234)
  8.  
  9. int main()
  10. {
  11. int running =1;
  12. void*shm = NULL;
  13. struct shared_use_st *shared = NULL;
  14. char buffer[BUFSIZ +1];//用於保存輸入的文本
  15. int shmid;
  16. //創建共享內存
  17. shmid = shmget((key_t)MEM_KEY,sizeof(struct shared_use_st),0666|IPC_CREAT);
  18. if(shmid ==-1)
  19. {
  20. fprintf(stderr,"shmget failed\n");
  21. exit(EXIT_FAILURE);
  22. }
  23. //將共享內存連接到當前進程的地址空間
  24. shm = shmat(shmid,(void*)0,0);
  25. if(shm ==(void*)-1)
  26. {
  27. fprintf(stderr,"shmat failed\n");
  28. exit(EXIT_FAILURE);
  29. }
  30. printf("Memory attached at %X\n",(int)shm);
  31. //設置共享內存
  32. shared =(struct shared_use_st*)shm;
  33. while(running)//向共享內存中寫數據
  34. {
  35. //數據還沒有被讀取,則等待數據被讀取,不能向共享內存中寫入文本
  36. while(shared->written ==1)
  37. {
  38. sleep(1);
  39. printf("Waiting...\n");
  40. }
  41. //向共享內存中寫入數據
  42. printf("Enter some text: ");
  43. fgets(buffer, BUFSIZ, stdin);
  44. strncpy(shared->text, buffer, TEXT_SZ);
  45. //寫完數據,設置written使共享內存段可讀
  46. shared->written =1;
  47. //輸入了end,退出循環(程序)
  48. if(strncmp(buffer,"end",3)==0)
  49. running =0;
  50. }
  51. //把共享內存從當前進程中分離
  52. if(shmdt(shm)==-1)
  53. {
  54. fprintf(stderr,"shmdt failed\n");
  55. exit(EXIT_FAILURE);
  56. }
  57. sleep(2);
  58. exit(EXIT_SUCCESS);
  59. }

 

代碼分析:

程序shmread創建共享內存,然后將它連接到自己的地址空間。在共享內存的開始處使用了一個結構struct_use_st。該結構中有個標志written,當共享內存中有其他進程向它寫入數據時,共享內存中的written被設置為0,程序等待。當它不為0時,表示沒有進程對共享內存寫入數據,程序就從共享內存中讀取數據並輸出,然后重置設置共享內存中的written0,即讓其可被shmwrite進程寫入數據。

程序shmwrite取得共享內存並連接到自己的地址空間中。檢查共享內存中的written,是否為0,若不是,表示共享內存中的數據還沒有被完,則等待其他進程讀取完成,並提示用戶等待。若共享內存的written0,表示沒有其他進程對共享內存進行讀取,則提示用戶輸入文本,並再次設置共享內存中的written1,表示寫完成,其他進程可對共享內存進行讀操作。

 關於前面的例子的安全性討論

這個程序是不安全的,當有多個程序同時向共享內存中讀寫數據時,問題就會出現。可能你會認為,可以改變一下written的使用方式,例如,只有當written0時進程才可以向共享內存寫入數據,而當一個進程只有在written不為0時才能對其進行讀取,同時把written進行加1操作,讀取完后進行減1操作。這就有點像文件鎖中的讀寫鎖的功能。咋看之下,它似乎能行得通。但是這都不是原子操作,所以這種做法是行不能的。試想當written0時,如果有兩個進程同時訪問共享內存,它們就會發現written0,於是兩個進程都對其進行寫操作,顯然不行。當written1時,有兩個進程同時對共享內存進行讀操作時也是如些,當這兩個進程都讀取完是,written就變成了-1.

要想讓程序安全地執行,就要有一種進程同步的進制,保證在進入臨界區的操作是原子操作。例如,可以使用前面所講的信號量來進行進程的同步。因為信號量的操作都是原子性的。

使用共享內存的優缺點

優點:我們可以看到使用共享內存進行進程間的通信真的是非常方便,而且函數的接口也簡單,數據的共享還使進程間的數據不用傳送,而是直接訪問內存,也加快了程序的效率。同時,它也不像匿名管道那樣要求通信的進程有一定的父子關系。

缺點:共享內存沒有提供同步的機制,這使得我們在使用共享內存進行進程間通信時,往往要借助其他的手段來進行進程間的同步工作。

四 更好的例子

1、server.c

 

  1. /*server.c:向共享內存中寫入People*/
  2. #include<stdio.h>
  3. #include<sys/types.h>
  4. #include<sys/ipc.h>
  5. #include<sys/sem.h>
  6. #include<string.h>
  7. #include"credis.h"
  8. int semid;
  9. int shmid;
  10. /*信號量的P操作*/
  11. void p()
  12. {
  13. struct sembuf sem_p;
  14. sem_p.sem_num=0;/*設置哪個信號量*/
  15. sem_p.sem_op=-1;/*定義操作*/
  16. if(semop(semid,&sem_p,1)==-1)
  17. printf("p operation is fail\n");
  18. /*semop函數自動執行信號量集合上的操作數組。
  19.    int semop(int semid, struct sembuf semoparray[], size_t nops);
  20.    semoparray是一個指針,它指向一個信號量操作數組。nops規定該數組中操作的數量。*/
  21. }
  22. /*信號量的V操作*/
  23. void v()
  24. {
  25. struct sembuf sem_v;
  26. sem_v.sem_num=0;
  27. sem_v.sem_op=1;
  28. if(semop(semid,&sem_v,1)==-1)
  29. printf("v operation is fail\n");
  30. }
  31. int main()
  32. {
  33. structPeople{
  34. char name[10];
  35. int age;
  36. };
  37. key_t semkey;
  38. key_t shmkey;
  39. semkey=ftok("../test/VenusDB.cbp",0);//用來產生唯一的標志符,便於區分信號量及共享內存
  40. shmkey=ftok("../test/main.c",0);
  41. /*創建信號量的XSI IPC*/
  42. semid=semget(semkey,1,0666|IPC_CREAT);//參數nsems,此時為中間值1,指定信號燈集包含信號燈的數目
  43. //0666|IPC_CREAT用來表示對信號燈的讀寫權限
  44. /*
  45. 從左向右:
  46. 第一位:0表示這是一個8進制數
  47. 第二位:當前用戶的經權限:6=110(二進制),每一位分別對就 可讀,可寫,可執行,6說明當前用戶可讀可寫不可執行
  48. 第三位:group組用戶,6的意義同上
  49. 第四位:其它用戶,每一位的意義同上,0表示不可讀不可寫也不可執行
  50. */
  51. if(semid==-1)
  52. printf("creat sem is fail\n");
  53. //創建共享內存
  54. shmid=shmget(shmkey,1024,0666|IPC_CREAT);//對共享內存
  55. if(shmid==-1)
  56. printf("creat shm is fail\n");
  57. /*設置信號量的初始值,就是資源個數*/
  58. union semun{
  59. int val;
  60. struct semid_ds *buf;
  61. unsignedshort*array;
  62. }sem_u;
  63. sem_u.val=1;/*設置變量值*/
  64. semctl(semid,0,SETVAL,sem_u);//初始化信號量,設置第0個信號量,p()操作為非阻塞的
  65. /*將共享內存映射到當前進程的地址中,之后直接對進程中的地址addr操作就是對共享內存操作*/
  66. structPeople*addr;
  67. addr=(structPeople*)shmat(shmid,0,0);//將共享內存映射到調用此函數的內存段
  68. if(addr==(structPeople*)-1)
  69. printf("shm shmat is fail\n");
  70. /*向共享內存寫入數據*/
  71. p();
  72. strcpy((*addr).name,"xiaoming");
  73. /*注意:①此處只能給指針指向的地址直接賦值,不能在定義一個 struct People people_1;addr=&people_1;因為addr在addr=(struct People*)shmat(shmid,0,0);時,已經由系統自動分配了一個地址,這個地址與共享內存相關聯,所以不能改變這個指針的指向,否則他將不指向共享內存,無法完成通信了。
  74. 注意:②給字符數組賦值的方法。剛才太虎了。。*/
  75. (*addr).age=10;
  76. v();
  77. /*將共享內存與當前進程斷開*/
  78. if(shmdt(addr)==-1)
  79. printf("shmdt is fail\n");
  80. }
2、clinet.c
  1. /*client.c:從共享內存中讀出People*/
  2. #include<stdio.h>
  3. #include<sys/types.h>
  4. #include<sys/ipc.h>
  5. #include<sys/sem.h>
  6. int semid;
  7. int shmid;
  8. /*信號量的P操作*/
  9. void p()
  10. {
  11. struct sembuf sem_p;
  12. sem_p.sem_num=0;
  13. sem_p.sem_op=-1;
  14. if(semop(semid,&sem_p,1)==-1)
  15. printf("p operation is fail\n");
  16. }
  17. /*信號量的V操作*/
  18. void v()
  19. {
  20. struct sembuf sem_v;
  21. sem_v.sem_num=0;
  22. sem_v.sem_op=1;
  23. if(semop(semid,&sem_v,1)==-1)
  24. printf("v operation is fail\n");
  25. }
  26. int main()
  27. {
  28. key_t semkey;
  29. key_t shmkey;
  30. semkey=ftok("../test/client/VenusDB.cbp",0);
  31. shmkey=ftok("../test/client/main.c",0);
  32. structPeople{
  33. char name[10];
  34. int age;
  35. };
  36. /*讀取共享內存和信號量的IPC*/
  37. semid=semget(semkey,0,0666);
  38. if(semid==-1)
  39. printf("creat sem is fail\n");
  40. shmid=shmget(shmkey,0,0666);
  41. if(shmid==-1)
  42. printf("creat shm is fail\n");
  43. /*將共享內存映射到當前進程的地址中,之后直接對進程中的地址addr操作就是對共享內存操作*/
  44. structPeople*addr;
  45. addr=(structPeople*)shmat(shmid,0,0);
  46. if(addr==(structPeople*)-1)
  47. printf("shm shmat is fail\n");
  48. /*從共享內存讀出數據*/
  49. p();
  50. printf("name:%s\n",addr->name);
  51. printf("age:%d\n",addr->age);
  52. v();
  53. /*將共享內存與當前進程斷開*/
  54. if(shmdt(addr)==-1)
  55. printf("shmdt is fail\n");
  56. /*IPC必須顯示刪除。否則會一直留存在系統中*/
  57. if(semctl(semid,0,IPC_RMID,0)==-1)
  58. printf("semctl delete error\n");
  59. if(shmctl(shmid,IPC_RMID,NULL)==-1)
  60. printf("shmctl delete error\n");
  61. }

五 父子進程共享內存的個子

  1. #include<stdio.h>
  2. #include<sys/types.h>
  3. #include<sys/ipc.h>
  4. #include<sys/sem.h>
  5. #define SHM_KEY 0x33
  6. #define SEM_KEY 0x44
  7. union semun {
  8. int val;
  9. struct semid_ds *buf;
  10. unsignedshort*array;
  11. };
  12. int P(int semid)
  13. {
  14. struct sembuf sb;
  15. sb.sem_num =0;
  16. sb.sem_op =-1;
  17. sb.sem_flg = SEM_UNDO;
  18. if(semop(semid,&sb,1)==-1){
  19. perror("semop");
  20. return-1;
  21. }
  22. return0;
  23. }
  24. int V(int semid)
  25. {
  26. struct sembuf sb;
  27. sb.sem_num =0;
  28. sb.sem_op =1;
  29. sb.sem_flg = SEM_UNDO;
  30. if(semop(semid,&sb,1)==-1){
  31. perror("semop");
  32. return-1;
  33. }
  34. return0;
  35. }
  36. int main(int argc,char**argv)
  37. {
  38. pid_t pid;
  39. int i, shmid, semid;
  40. int*ptr;
  41. union semun semopts;
  42. /* 創建一塊共享內存, 存一個int變量 */
  43. if((shmid = shmget(SHM_KEY,sizeof(int), IPC_CREAT |0600))==-1){
  44. perror("msgget");
  45. }
  46. /* 將共享內存映射到進程, fork后子進程可以繼承映射 */
  47. if((ptr =(int*)shmat(shmid, NULL,0))==(void*)-1){
  48. perror("shmat");
  49. }
  50. *ptr =0;
  51. /* 創建一個信號量用來同步共享內存的操作 */
  52. if((semid = semget(SEM_KEY,1, IPC_CREAT |0600))==-1){
  53. perror("semget");
  54. }
  55. /* 初始化信號量 */
  56. semopts.val =1;
  57. if(semctl(semid,0, SETVAL, semopts)<0){
  58. perror("semctl");
  59. }
  60. if((pid = fork())<0){
  61. perror("fork");
  62. }elseif(pid ==0){/* Child */
  63. /* 子進程對共享內存加1 */
  64. for(i =0; i <100; i++){
  65. P(semid);
  66. (*ptr)++;
  67. V(semid);
  68. printf("child: %d\n",*ptr);
  69. }
  70. }else{/* Parent */
  71. /* 父進程對共享內存減1 */
  72. for(i =0; i <100; i++){
  73. P(semid);
  74. (*ptr)--;
  75. V(semid);
  76. printf("parent: %d\n",*ptr);
  77. }
  78. waitpid(pid);
  79. sleep(2);
  80. /* 如果同步成功, 共享內存的值為0 */
  81. printf("finally: %d\n",*ptr);
  82. }
  83. return0;
  84. }
 
 

 

 






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM