首頁  >  文章  >  系統教程  >  Linux 進程間通訊的一種高效方法:使用共享內存

Linux 進程間通訊的一種高效方法:使用共享內存

王林
王林轉載
2024-02-11 19:00:131175瀏覽

以下將講解進程間通訊的另一種方式,使用共享記憶體。

一、什麼是共享記憶體

顧名思義,共享記憶體就是允許兩個不相關的程序存取同一個邏輯記憶體。共享記憶體是在兩個正在運行的進程之間共享和傳遞資料的一種非常有效的方式。不同進程之間共享的記憶體通常安排為同一段實體記憶體。進程可以將同一段共享記憶體連接到它們自己的位址空間中,所有進程都可以存取共享記憶體中的位址,就好像它們是由用C語言函數malloc分配的記憶體一樣。而如果某個進程向共享記憶體寫入數據,所做的改動將立即影響到可以存取同一段共享記憶體的任何其他進程。

Linux 进程间通信的一种高效方法:使用共享内存

#特別提醒:共享記憶體並未提供同步機制,也就是說,在第一個進程結束對共享記憶體的寫入操作之前,並無自動機制可以阻止第二個進程開始對它進行讀取。所以我們通常需要用其他的機制來同步對共享記憶體的訪問,例如前面說到的信號量。有關信號量的更多內容,可以查閱我的另一篇文章:Linux進程間通訊-使用信號量

二、共享記憶體的使得

與信號量一樣,在Linux中也提供了一組函數介面用於使用共享內存,而且使用共享共存的介面還與信號量的非常相似,而且比使用信號量的接口來得簡單。它們聲明在頭檔 sys/shm.h中。

1、shmget函數

此函數用來建立共享內存,它的原型是:

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

#第一個參數,與信號量的semget函數一樣,程式需要提供一個參數key(非0整數),它有效地為共享記憶體段命名,shmget函數成功時傳回一個與key相關的共享記憶體標識符(非負整數),用於後續的共享記憶體函數。呼叫失敗返回-1.

不相關的進程可以透過該函數的返回值存取同一共享內存,它代表程式可能要使用的某個資源,程式對所有共享內存的存取都是間接的,程式先透過調用shmget函數並提供一個鍵,再由系統產生一個對應的共享記憶體標識符(shmget函數的返回值),只有shmget函數才直接使用信號量鍵,所有其他的信號量函數使用由semget函數傳回的信號量識別碼。

第二個參數,size以位元組為單位指定需要共享的記憶體容量

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

2、shmat函數

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

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

第一個參數,shm_id是由shmget函數傳回的共享記憶體標識。

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

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

呼叫成功時傳回一個指向共享記憶體第一個位元組的指針,如果呼叫失敗返回-1.

3、shmdt函數

此函數用於將共享記憶體從目前進程中分離。請注意,將共享記憶體分離並不是刪除它,只是使該共享記憶體對當前進程不再可用。它的原型如下:

 1. **int** shmdt(**const** **void** *shmaddr); 

參數shmaddr是shmat函數傳回的位址指針,呼叫成功時回傳0,失敗時回傳-1.

4、shmctl函數

與信號量的semctl函數一樣,用來控制共享內存,它的原型如下:

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

第一个参数,shm_id是shmget函数返回的共享内存标识符。

第二个参数,command是要采取的操作,它可以取下面的三个值 :

IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。

IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值

IPC_RMID:删除共享内存段

第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。

shmid_ds结构至少包括以下成员:

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

三、使用共享内存进行进程间通信

说了这么多,又到了实战的时候了。下面就以两个不相关的进程来说明进程间如何通过共享内存来进行通信。其中一个文件shmread.c创建共享内存,并读取其中的信息,另一个文件shmwrite.c向共享内存中写入数据。为了方便操作和数据结构的统一,为这两个文件定义了相同的数据结构,定义在文件shmdata.c中。结构shared_use_st中的written作为一个可读或可写的标志,非0:表示可读,0表示可写,text则是内存中的文件。

shmdata.h的源代码如下:

1. \#ifndef _SHMDATA_H_HEADER 
2. \#define _SHMDATA_H_HEADER 
3.  
4. \#define TEXT_SZ 2048 
5.  
6. **struct** shared_use_st 
7. { 
8.   **int** written;//作为一个标志,非0:表示可读,0表示可写 
9.   **char** text[TEXT_SZ];//记录写入和读取的文本 
10. }; 
11.  
12. \#endif 

源文件shmread.c的源代码如下:

  1. 1. \#include  
    2. \#include  
    3. \#include  
    4. \#include  
    5. \#include "shmdata.h" 
    6.  
    7. **int** main() 
    8. { 
    9.   **int** running = 1;//程序是否继续运行的标志 
    10.   **void** *shm = NULL;//分配的共享内存的原始首地址 
    11.   **struct** shared_use_st *shared;//指向shm 
    12.   **int** shmid;//共享内存标识符 
    13.   //创建共享内存 
    14.   shmid = shmget((key_t)1234, **sizeof**(**struct** shared_use_st), 0666|IPC_CREAT); 
    15.   **if**(shmid == -1) 
    16.   { 
    17. ​    fprintf(stderr, "shmget failed\n"); 
    18. ​    exit(EXIT_FAILURE); 
    19.   } 
    20.   //将共享内存连接到当前进程的地址空间 
    21.   shm = shmat(shmid, 0, 0); 
    22.   **if**(shm == (**void***)-1) 
    23.   { 
    24. ​    fprintf(stderr, "shmat failed\n"); 
    25. ​    exit(EXIT_FAILURE); 
    26.   } 
    27.   printf("\nMemory attached at %X\n", (**int**)shm); 
    28.   //设置共享内存 
    29.   shared = (**struct** shared_use_st*)shm; 
    30.   shared->written = 0; 
    31.   **while**(running)//读取共享内存中的数据 
    32.   { 
    33. ​    //没有进程向共享内存定数据有数据可读取 
    34. ​    **if**(shared->written != 0) 
    35. ​    { 
    36. ​      printf("You wrote: %s", shared->text); 
    37. ​      sleep(rand() % 3); 
    38. ​      //读取完数据,设置written使共享内存段可写 
    39. ​      shared->written = 0; 
    40. ​      //输入了end,退出循环(程序) 
    41. ​      **if**(strncmp(shared->text, "end", 3) == 0) 
    42. ​        running = 0; 
    43. ​    } 
    44. ​    **else**//有其他进程在写数据,不能读取数据 
    45. ​      sleep(1); 
    46.   } 
    47.   //把共享内存从当前进程中分离 
    48.   **if**(shmdt(shm) == -1) 
    49.   { 
    50. ​    fprintf(stderr, "shmdt failed\n"); 
    51. ​    exit(EXIT_FAILURE); 
    52.   } 
    53.   //删除共享内存 
    54.   **if**(shmctl(shmid, IPC_RMID, 0) == -1) 
    55.   { 
    56. ​    fprintf(stderr, "shmctl(IPC_RMID) failed\n"); 
    57. ​    exit(EXIT_FAILURE); 
    58.   } 
    59.   exit(EXIT_SUCCESS); 
    60. } 
    

源文件shmwrite.c的源代码如下:

1. \#include  
2. \#include  
3. \#include  
4. \#include  
5. \#include  
6. \#include "shmdata.h" 
7.  
8. **int** main() 
9. { 
10.   **int** running = 1; 
11.   **void** *shm = NULL; 
12.   **struct** shared_use_st *shared = NULL; 
13.   **char** buffer[BUFSIZ + 1];//用于保存输入的文本 
14.   **int** shmid; 
15.   //创建共享内存 
16.   shmid = shmget((key_t)1234, **sizeof**(**struct** shared_use_st), 0666|IPC_CREAT); 
17.   **if**(shmid == -1) 
18.   { 
19. ​    fprintf(stderr, "shmget failed\n"); 
20. ​    exit(EXIT_FAILURE); 
21.   } 
22.   //将共享内存连接到当前进程的地址空间 
23.   shm = shmat(shmid, (**void***)0, 0); 
24.   **if**(shm == (**void***)-1) 
25.   { 
26. ​    fprintf(stderr, "shmat failed\n"); 
27. ​    exit(EXIT_FAILURE); 
28.   } 
29.   printf("Memory attached at %X\n", (**int**)shm); 
30.   //设置共享内存 
31.   shared = (**struct** shared_use_st*)shm; 
32.   **while**(running)//向共享内存中写数据 
33.   { 
34. ​    //数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本 
35. ​    **while**(shared->written == 1) 
36. ​    { 
37. ​      sleep(1); 
38. ​      printf("Waiting...\n"); 
39. ​    } 
40. ​    //向共享内存中写入数据 
41. ​    printf("Enter some text: "); 
42. ​    fgets(buffer, BUFSIZ, stdin); 
43. ​    strncpy(shared->text, buffer, TEXT_SZ); 
44. ​    //写完数据,设置written使共享内存段可读 
45. ​    shared->written = 1; 
46. ​    //输入了end,退出循环(程序) 
47. ​    **if**(strncmp(buffer, "end", 3) == 0) 
48. ​      running = 0; 
49.   } 
50.   //把共享内存从当前进程中分离 
51.   **if**(shmdt(shm) == -1) 
52.   { 
53. ​    fprintf(stderr, "shmdt failed\n"); 
54. ​    exit(EXIT_FAILURE); 
55.   } 
56.   sleep(2); 
57.   exit(EXIT_SUCCESS); 
58. } 

再来看看运行的结果:

分析:

1、程序shmread创建共享内存,然后将它连接到自己的地址空间。在共享内存的开始处使用了一个结构struct_use_st。该结构中有个标志written,当共享内存中有其他进程向它写入数据时,共享内存中的written被设置为0,程序等待。当它不为0时,表示没有进程对共享内存写入数据,程序就从共享内存中读取数据并输出,然后重置设置共享内存中的written为0,即让其可被shmwrite进程写入数据。

2、程序shmwrite取得共享内存并连接到自己的地址空间中。检查共享内存中的written,是否为0,若不是,表示共享内存中的数据还没有被完,则等待其他进程读取完成,并提示用户等待。若共享内存的written为0,表示没有其他进程对共享内存进行读取,则提示用户输入文本,并再次设置共享内存中的written为1,表示写完成,其他进程可对共享内存进行读操作。

四、关于前面的例子的安全性讨论

这个程序是不安全的,当有多个程序同时向共享内存中读写数据时,问题就会出现。可能你会认为,可以改变一下written的使用方式,例如,只有当written为0时进程才可以向共享内存写入数据,而当一个进程只有在written不为0时才能对其进行读取,同时把written进行加1操作,读取完后进行减1操作。这就有点像文件锁中的读写锁的功能。咋看之下,它似乎能行得通。但是这都不是原子操作,所以这种做法是行不能的。试想当written为0时,如果有两个进程同时访问共享内存,它们就会发现written为0,于是两个进程都对其进行写操作,显然不行。当written为1时,有两个进程同时对共享内存进行读操作时也是如些,当这两个进程都读取完是,written就变成了-1.

要想让程序安全地执行,就要有一种进程同步的进制,保证在进入临界区的操作是原子操作。例如,可以使用前面所讲的信号量来进行进程的同步。因为信号量的操作都是原子性的。

五、使用共享内存的优缺点

1、优点:我们可以看到使用共享内存进行进程间的通信真的是非常方便,而且函数的接口也简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,也加快了程序的效率。同时,它也不像匿名管道那样要求通信的进程有一定的父子关系。

2、缺点:共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。

以上是Linux 進程間通訊的一種高效方法:使用共享內存的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:lxlinux.net。如有侵權,請聯絡admin@php.cn刪除