Linux共用記憶體

來源:互聯網
上載者:User

共用記憶體共用記憶體是處理序間通訊中最簡單的方式之一。共用記憶體允許兩個或更多進程訪問同一塊記憶體,就如同 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 中的程式展示了共用記憶體塊的使用。 </p><p>  代碼 5.1 (shm.c) 嘗試共用記憶體 </p><p>  #include <stdio.h> </p><p>  #include <sys/shm.h> </p><p>  #include <sys/stat.h> </p><p>  int main() </p><p>  { </p><p>  int segment_id; </p><p>  char* shared_memory; </p><p>  struct shmid_ds shmbuffer; </p><p>  int segment_size; </p><p>  const int shared_segment_size = 0x6400; /* 分配一個共用記憶體塊 */ </p><p>  segment_id = shmget(IPC_PRIVATE, shared_segment_size, IPC_CREAT|IPC_EXCL|S_IRUSR|S_IWUSR ); /* 綁定到共用記憶體塊 */ </p><p>  shared_memory = (char*)shmat(segment_id, 0, 0); </p><p>  printf("shared memory attached at address %p/n", shared_memory); /* 確定共用記憶體的大小 */ </p><p>  shmctl(segment_id, IPC_STAT, &shmbuffer); </p><p>  segment_size = shmbuffer.shm_segsz; </p><p>  printf("segment size: %d/n", segment_size); </p><p>  sprintf(shared_memory, "Hello, world."); /* 在共用記憶體中寫入一個字串 */ </p><p>  shmdt(shared_memory); /* 脫離該共用記憶體塊 */ </p><p>  shared_memory = (char*)shmat(segment_id, (void*) 0x500000, 0);/* 重新綁定該記憶體塊 */ </p><p>  printf("shared memory reattached at address %p/n", shared_memory); </p><p>  printf("%s/n", shared_memory); /* 輸出共用記憶體中的字串 */ </p><p>  shmdt(shared_memory); /* 脫離該共用記憶體塊 */ </p><p>  shmctl(segment_id, IPC_RMID, 0);/* 釋放這個共用記憶體塊 */ </p><p>  return 0; </p><p>  } </p><p>

調試

  使用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建立新的共用記憶體塊的時候也不能保證訪問的獨佔性。同時,多個使用共用記憶體塊的進程之間必須協調使用同一個索引值。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.