linux共享記憶體

maojunxu發表於2012-09-07

共享記憶體共享記憶體是程式間通訊中最簡單的方式之一。共享記憶體允許兩個或更多程式訪問同一塊記憶體,就如同 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
“, shared_memory); /* 確定共享記憶體的大小 */

  shmctl(segment_id, IPC_STAT, &shmbuffer);

  segment_size = shmbuffer.shm_segsz;

  printf(“segment size: %d
“, 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
“, shared_memory);

  printf(“%s
“, 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 0x00000000 1627649 user 640 25600 0 如果這個共享記憶體塊在程式結束後沒有被刪除而是被錯誤地保留下來,您可以用ipcrm命令刪除它。

  % ipcrm shm 1627649

優點和缺點

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


相關文章