linux共享記憶體

共享記憶體進程間通信中最簡單的方式之一。共享記憶體允許兩個或更多進程訪問同一塊記憶體,就如同 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 對象的指針作為第三個參數。
要刪除一個共享記憶體塊,則應將 IPC_RMID 作為第二個參數,而將 NULL 作為第三個參數。當最後一個綁定該共享記憶體塊的進程與其脫離時,該共享記憶體塊將被刪除。
您應當在結束使用每個共享記憶體塊的時候都使用 shmctl 進行釋放,以防止超過系統所允許的共享記憶體塊的總數限制。調用 exit 和 exec 會使進程脫離共享記憶體塊,但不會刪除這個記憶體塊。 要查看其它有關共享記憶體塊的操作的描述,請參考shmctl函式的手冊頁。

示例程式

代碼 5.1 中的程式展示了共享記憶體塊的使用。
代碼 5.1 (shm.c) 嘗試共享記憶體
#include <stdio.h>
#include <sys/shm.h>
#include <sys/stat.h>
int main()
{
int segment_id;
char* shared_memory;
struct shmid_ds shmbuffer;
int segment_size;
const int shared_segment_size = 0x6400; /* 分配一個共享記憶體塊 */
segment_id = shmget(IPC_PRIVATE, shared_segment_size, IPC_CREAT|IPC_EXCL|S_IRUSR|S_IWUSR ); /* 綁定到共享記憶體塊 */
shared_memory = (char*)shmat(segment_id, 0, 0);
printf("shared memory attached at address %p\n", shared_memory); /* 確定共享記憶體的大小 */
shmctl(segment_id, IPC_STAT, &shmbuffer);
segment_size = shmbuffer.shm_segsz;
printf("segment size: %d\n", segment_size);
sprintf(shared_memory, "Hello, world."); /* 在共享記憶體中寫入一個字元串 */
shmdt(shared_memory); /* 脫離該共享記憶體塊 */
shared_memory = (char*)shmat(segment_id, (void*) 0x500000, 0);/* 重新綁定該記憶體塊 */
printf("shared memory reattached at address %p\n", shared_memory);
printf("%s\n", shared_memory); /* 輸出共享記憶體中的字元串 */
shmdt(shared_memory); /* 脫離該共享記憶體塊 */
shmctl(segment_id, IPC_RMID, 0);/* 釋放這個共享記憶體塊 */
return 0;
}

調試

使用ipcs 命令可用於查看系統中包括共享記憶體在內的進程間通信機制的信息。指定-m參數以獲取有關共享記憶體的信息。例如,以下的示例表示有一個編號為1627649的共享記憶體塊正在使用中:
% ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x000
00000 —— 1627649 user 640 25600 0 如果這個共享記憶體塊在程式結束後沒有被刪除而是被錯誤地保留下來,您可以用ipcrm命令刪除它。
% ipcrm shm 1627649

優點缺點

共享記憶體塊提供了在任意數量的進程之間進行高效雙向通信的機制。每個使用者都可以讀取寫入數據,但是所有程式之間必須達成並遵守一定的協定,以防止諸如在讀取信息之前覆寫記憶體空間等競爭狀態的出現。不幸的是,Linux無法嚴格保證提供對共享記憶體塊的獨占訪問,甚至是在您通過使用IPC_PRIVATE創建新的共享記憶體塊的時候也不能保證訪問的獨占性。 同時,多個使用共享記憶體塊的進程之間必須協調使用同一個鍵值。

相關詞條

熱門詞條

聯絡我們