-
1 # 額呃呃呃合夥
-
2 # 南極影解
共享記憶體是程序間通訊中最簡單的方式之一。共享記憶體允許兩個或更多程序訪問同一塊記憶體,就如同malloc()函式向不同程序返回了指向同一個物理記憶體區域的指標。當一個程序改變了這塊地址中的內容的時候,其它程序都會察覺到這個更改。
本地通訊因為所有程序共享同一塊記憶體,共享記憶體在各種程序間通訊方式中具有最高的效率。訪問共享記憶體區域和訪問程序獨有的記憶體區域一樣快,並不需要透過系統呼叫或者其它需要切入核心的過程來完成。同時它也避免了對資料的各種不必要的複製。
因為系統核心沒有對訪問共享記憶體進行同步,您必須提供自己的同步措施。例如,在資料被寫入之前不允許程序從共享記憶體中讀取資訊、不允許兩個程序同時向同一個共享記憶體地址寫入資料等。解決這些問題的常用方法是透過使用訊號量進行同步。不過,我們的程式中只有一個程序訪問了共享記憶體,因此在集中展示了共享記憶體機制的同時,我們避免了讓程式碼被同步邏輯搞得混亂不堪。
記憶體模型要使用一塊共享記憶體,程序必須首先分配它。隨後需要訪問這個共享記憶體塊的每一個程序都必須將這個共享記憶體繫結到自己的地址空間中。當完成通訊之後,所有程序都將脫離共享記憶體,並且由一個程序釋放該共享記憶體塊。
理解 Linux 系統記憶體模型可以有助於解釋這個繫結的過程。在 Linux 系統中,每個程序的虛擬記憶體是被分為許多頁面的。這些記憶體頁面中包含了實際的資料。每個程序都會維護一個從記憶體地址到虛擬記憶體頁面之間的對映關係。儘管每個程序都有自己的記憶體地址,不同的程序可以同時將同一個記憶體頁面對映到自己的地址空間中,從而達到共享記憶體的目的。
分配一個新的共享記憶體塊會建立新的記憶體頁面。因為所有程序都希望共享對同一塊記憶體的訪問,只應由一個程序建立一塊新的共享記憶體。再次分配一塊已經存在的記憶體塊不會建立新的頁面,而只是會返回一個標識該記憶體塊的識別符號。一個程序如需使用這個共享記憶體塊,則首先需要將它繫結到自己的地址空間中。這樣會建立一個從程序本身虛擬地址到共享頁面的對映關係。當對共享記憶體的使用結束之後,這個對映關係將被刪除。當再也沒有程序需要使用這個共享記憶體塊的時候,必須有一個(且只能是一個)程序負責釋放這個被共享的記憶體頁面。
所有共享記憶體塊的大小都必須是系統頁面大小的整數倍。系統頁面大小指的是系統中單個記憶體頁面包含的位元組數。在 Linux 系統中,記憶體頁面大小是4KB,不過您仍然應該透過呼叫 getpagesize 獲取這個值。
分配程序透過呼叫shmget(Shared Memory GET,獲取共享記憶體)來分配一個共享記憶體塊。
該函式的第一個引數是一個用來標識共享記憶體塊的鍵值。彼此無關的程序可以透過指定同一個鍵以獲取對同一個共享記憶體塊的訪問。不幸的是,其它程式也可能挑選了同樣的特定值作為自己分配共享記憶體的鍵值,從而產生衝突。用特殊常量IPC_PRIVATE作為鍵值可以保證系統建立一個全新的共享記憶體塊。
該函式的第二個引數指定了所申請的記憶體塊的大小。因為這些記憶體塊是以頁面為單位進行分配的,實際分配的記憶體塊大小將被擴大到頁面大小的整數倍。
第三個引數是一組標誌,透過特定常量的按位或操作來shmget。這些特定常量包括:
IPC_CREAT:這個標誌表示應建立一個新的共享記憶體塊。透過指定這個標誌,我們可以建立一個具有指定鍵值的新共享記憶體塊。
IPC_EXCL:這個標誌只能與 IPC_CREAT 同時使用。當指定這個標誌的時候,如果已有一個具有這個鍵值的共享記憶體塊存在,則shmget會呼叫失敗。也就是說,這個標誌將使執行緒獲得一個“獨有”的共享記憶體塊。如果沒有指定這個標誌而系統中存在一個具有相同鍵值的共享記憶體塊,shmget會返回這個已經建立的共享記憶體塊,而不是重新建立一個。
模式標誌:這個值由9個位組成,分別表示屬主、屬組和其它使用者對該記憶體塊的訪問許可權。其中表示執行許可權的位將被忽略。指明訪問許可權的一個簡單辦法是利用<sys/stat.h>中指定,並且在手冊頁第二節stat條目中說明了的常量指定。例如,S_IRUSR和S_IWUSR分別指定了該記憶體塊屬主的讀寫許可權,而 S_IROTH和S_IWOTH則指定了其它使用者的讀寫許可權。 下面例子中shmget函式建立了一個新的共享記憶體塊(當shm_key已被佔用時則獲取對一個已經存在共享記憶體塊的訪問),且只有屬主對該記憶體塊具有讀寫許可權,其它使用者不可讀寫。
int segment_id = shmget (shm_key, getpagesize (), IPC_CREAT | S_IRUSR| S_IWUSR ); 如果呼叫成功,shmget將返回一個共享記憶體識別符號。如果該共享記憶體塊已經存在,系統會檢查訪問許可權,同時會檢查該記憶體塊是否被標記為等待摧毀狀態。
繫結脫離要讓一個程序獲取對一塊共享記憶體的訪問,這個程序必須先呼叫 shmat(SHared Memory Attach,繫結到共享記憶體)。將 shmget 返回的共享記憶體識別符號 SHMID 傳遞給這個函式作為第一個引數。該函式的第二個引數是一個指標,指向您希望用於對映該共享記憶體塊的程序記憶體地址;如果您指定NULL則Linux會自動選擇一個合適的地址用於對映。第三個引數是一個標誌位,包含了以下選項:
SHM_RND表示第二個引數指定的地址應被向下靠攏到記憶體頁面大小的整數倍。如果您不指定這個標誌,您將不得不在呼叫shmat的時候手工將共享記憶體塊的大小按頁面大小對齊。 SHM_RDONLY表示這個記憶體塊將僅允許讀取操作而禁止寫入。 如果這個函式呼叫成功則會返回繫結的共享記憶體塊對應的地址。透過 fork 函式建立的子程序同時繼承這些共享記憶體塊;如果需要,它們可以主動脫離這些共享記憶體塊。 當一個程序不再使用一個共享記憶體塊的時候應透過呼叫 shmdt(Shared Memory Detach,脫離共享記憶體塊)函式與該共享記憶體塊脫離。將由 shmat 函式返回的地址傳遞給這個函式。如果當釋放這個記憶體塊的程序是最後一個使用該記憶體塊的程序,則這個記憶體塊將被刪除。對 exit 或任何exec族函式的呼叫都會自動使程序脫離共享記憶體塊。
控制釋放呼叫 shmctl("Shared Memory Control",控制共享記憶體)函式會返回一個共享記憶體塊的相關資訊。同時 shmctl 允許程式修改這些資訊。該函式的第一個引數是一個共享記憶體塊標識。
要獲取一個共享記憶體塊的相關資訊,則為該函式傳遞 IPC_STAT 作為第二個引數,同時傳遞一個指向一個 struct shmid_ds 物件的指標作為第三個引數。
您應當在結束使用每個共享記憶體塊的時候都使用 shmctl 進行釋放,以防止超過系統所允許的共享記憶體塊的總數限制。呼叫 exit 和 exec 會使程序脫離共享記憶體塊,但不會刪除這個記憶體塊。 要檢視其它有關共享記憶體塊的操作的描述,請參考shmctl函式的手冊頁。
優點缺點共享記憶體塊提供了在任意數量的程序之間進行高效雙向通訊的機制。每個使用者都可以讀取寫入資料,但是所有程式之間必須達成並遵守一定的協議,以防止諸如在讀取資訊之前覆寫記憶體空間等競爭狀態的出現。不幸的是,Linux無法嚴格保證提供對共享記憶體塊的獨佔訪問,甚至是在您透過使用IPC_PRIVATE建立新的共享記憶體塊的時候也不能保證訪問的獨佔性。 同時,多個使用共享記憶體塊的程序之間必須協調使用同一個鍵值。
回覆列表
Linux共享記憶體可以不用加鎖,不過需要一種機制來標記共享記憶體的讀寫狀態; 也就是說要讓兩個程序知道:
1)負責寫入的程序,必須知道當前共享記憶體是否可以寫入,上一次的寫入內容是否有被負責讀取的程序讀走;
2)負責讀取的程序,必須知道當前共享記憶體是否需要讀取,防止重複讀取。 一般的這種標記機制是透過以下方式來簡單實現: 1)透過讀寫鎖來控制; 2)共享記憶體上設定一個地方,專門存放當前共享記憶體的讀寫狀態;