天天看點

IPC——共享記憶體

Linux程序間通信——使用共享記憶體

下面将講解程序間通信的另一種方式,使用共享記憶體。

一、什麼是共享記憶體

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

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

二、共享記憶體的獲得

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

1、shmget函數

該函數用來建立共享記憶體,它的原型為:

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

不相關的程序可以通過該函數的傳回值通路同一共享記憶體,它代表程式可能要使用的某個資源,程式對所有共享記憶體的通路都是間接的,程式先通過調用shmget函數并提供一個鍵,再由系統生成一個相應的共享記憶體辨別符(shmget函數的傳回值),隻有shmget函數才直接使用信号量鍵,所有其他的信号量函數使用由semget函數傳回的信号量辨別符。

第二個參數,size以位元組為機關指定需要共享的記憶體容量,如果正在建立一個新段,則必須指定size。如果正在引用一個現存的段,則size指定為0.當建立一個新段,段内的内容初始化為0.

第三個參數,shmflg是權限标志,它的作用與open函數的mode參數一樣,如果要想在key辨別的共享記憶體不存在時,建立它的話,可以與IPC_CREAT做或操作。共享記憶體的權限标志與檔案的讀寫權限一樣,舉例來說,0644,它表示允許一個程序建立的共享記憶體被記憶體建立者所擁有的程序向共享記憶體讀取和寫入資料,同時其他使用者建立的程序隻能讀取共享記憶體。

2、shmat函數

第一次建立完共享記憶體時,它還不能被任何程序通路,shmat函數的作用就是用來啟動對該共享記憶體的通路,并把共享記憶體連接配接到目前程序的位址空間。它的原型如下:

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

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

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

調用成功時傳回一個指向共享記憶體第一個位元組的指針,如果調用失敗傳回-1.如果執行成功,那麼核心将使該記憶體共享存儲段shmid_ds結構中的shm_nattch計數器值加1.

3、shmdt函數

該函數用于将共享記憶體從目前程序中分離。注意,将共享記憶體分離并不是删除它,隻是使該共享記憶體對目前程序不再可用。它的原型如下:

參數shmaddr是shmat函數傳回的位址指針,調用成功時傳回0,失敗時傳回-1。如果成功,shmdt将使相關shmid_ds結構中的shm_nattch計數器值減1.

4、shmctl函數

與信号量的semctl函數一樣,用來控制共享記憶體,它的原型如下:

第一個參數,shm_id是shmget函數傳回的共享記憶體辨別符。

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

    IPC_STAT:把shmid_ds結構中的資料設定為共享記憶體的目前關聯值,即用共享記憶體的目前關聯值覆寫shmid_ds的值。

    IPC_SET:如果程序有足夠的權限,就把共享記憶體的目前關聯值設定為shmid_ds結構中給出的值

    IPC_RMID:删除共享記憶體段

第三個參數,buf是一個結構指針,它指向共享記憶體模式和通路權限的結構。

shmid_ds結構至少包括以下成員:

三、使用共享記憶體進行程序間通信

說了這麼多,又到了實戰的時候了。下面就以兩個不相關的程序來說明程序間如何通過共享記憶體來進行通信。其中一個檔案shmread.c建立共享記憶體,并讀取其中的資訊,另一個檔案shmwrite.c向共享記憶體中寫入資料。為了友善操作和資料結構的統一,為這兩個檔案定義了相同的資料結構,定義在檔案shmdata.c中。結構shared_use_st中的written作為一個可讀或可寫的标志,非0:表示可讀,0表示可寫,text則是記憶體中的檔案。

shmdata.c的源代碼如下:

源檔案shmread.c的源代碼如下:

源檔案shmwrite.c的源代碼如下:

再來看看運作的結果:

IPC——共享記憶體

分析:

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、缺點:共享記憶體沒有提供同步的機制,這使得我們在使用共享記憶體進行程序間通信時,往往要借助其他的手段來進行程序間的同步工作。