【Linux】進程間通信之共享內存

爲什麼進程間需要通信?

1、數據傳輸:一個進程需要將它的數據發送給另一個進程。
2、資源共享:多個進程之間享受同樣的資源
3、通知事件:一個進程需要向另一個或另一組進程發送消息,通知它們發生了某種事件。
4、進程控制:有些進程希望完全控制另一個進程的執行(如Debug進程),此時控制進程希望能夠攔截另一個進程的所有操作,並能夠即使知道它的狀態改變。

Linux進程間通信(IPC)由以下幾部分發展而來:
1、UNIX進程間通信
2、基於System V進程間通信
3、POSIX進程間通信
IPC(Inter-Process Communication,進程間通信)對象的介紹
System V 的IPC對象有共享內存、消息隊列、信號燈。


注意:在IPC的通信模式下,不管是使用消息隊列還是共享內存,甚至是信號燈,每個IPC的對象都有唯一的名字,稱爲"鍵"(key)。通過"鍵",進程能夠識別所用的對象。"鍵"與IPC對象的關係就如同文件名稱於文件,通過文件名,進程能夠讀寫文件內的數據,甚至多個進程能夠公用一個文件。而在 IPC的通訊模式下,通過"鍵"的使用也使得一個IPC對象能爲多個進程所共用。

1、共享內存

       共享內存就是允許兩個不相關的進程訪問同一邏輯內存(物理內存)。進程可以將同一段共享內存連接到它們自己的地址空間中,所有進程都可以訪問共享內存的地址,就好像它們用C語言函數malloc分配的內存一樣。而如果某個進程向共享內存寫入數據,所做的改動將立即影響到可以訪問同一段共享內存的任何其他進程。
       共享內存是在兩個正在運行的進程之間共享和傳遞數據的一種非常有效的方式。但共享內存並未提供同步機制。也就是說,在第一個進程對共享內存的寫操作之前,並無自動機制可以阻止第二個進程開始對它進行讀取。所有我們通常需要用其他的機制來同步對共享內存的訪問。
特點:
      1)共享內存是進程間通信最快的方式。
      2)共享內存沒有保護機制,需要信號量控制。
      3)共享內存的基本單位是頁,即大小最小是4K,且是向上取整數頁的。
      4)共享內存的生命週期是隨內核的。
      每個進程都有地址空間,地址空間到物理內存利用:mmu+頁表;物理內存中共享一段緩衝區,通過各自的頁表+mmu映射到各自的虛擬地址,如下圖所示:

2、共享內存函數及使用

       兩個不同進程A、B共享內存的意思是,同一塊物理內存被映射到進程A、B各自的進程地址空間。進程A可以即時看到進程B對共享內存中數據的更新,反之亦然。由於多個進程共享同一塊內存區域,必然需要某種同步機制,互斥鎖和信號量都可以。
      共享內存與信號量一樣,在Linux中也提供了一組函數接口用於使用共享內存,它們的頭文件爲sys/shm.h中。
下面對此進行簡單說明。
1)shmget函數
該函數用來創建共享內存,它的原型爲:

-------key與信號量的semget函數一樣,程序需要提供一個參數key(非0整數),它有效地爲共享內存段命名。
-------size以字節爲單位指定需要共享的內存容量
-------shmflg是權限標誌。它的作用與open函數的mode參數一樣,如果要想在key標識的共享內存不存在時,創建它的話,可以             與IPC_CREAT做或操作。共享內存的權限標誌與文件的讀寫權限一樣,舉例來說,0644,它表示允許一個進程創建的共享內存           被內存創建者所擁有的進程向共享內存讀取和寫入數據,同時其他用戶創建的進程只能讀取共享內存。
調用成功時返回一個與key相關的共享內存標識符(非負整數),用於後續的共享內存函數。調用失敗返回-1。

2)shmat函數
第一次創建完共享內存時,它還不能被任何進程訪問,shmat函數的作用就是用來啓動對該共享內存的訪問,並把共享內存連接到當前進程的地址空間。它的原型如下:

-------shm_id是由shmget函數返回的共享內存標識。
-------shm_addr指定共享內存連接到當前進程中的地址位置,通常爲空,表示讓系統來選擇共享內存的地址。
-------shmflg是一組標誌位,通常爲0。
調用成功時返回一個指向共享內存第一個字節的指針,如果調用失敗返回-1.

3)shmdt函數
該函數用於將共享內存從當前進程中分離。
注意:將共享內存分離並不是刪除它,只是使該共享內存對當前進程不再可用。它的原型如下:

參數shmaddr是shmat函數返回的地址指針,調用成功時返回0,失敗時返回-1.

4)shmctl函數
與信號量的semctl函數一樣,用來控制共享內存,它的原型如下:

-------shm_id是shmget函數返回的共享內存標識符。
-------command是要採取的操作,它可以取下面的三個值 :
          IPC_STAT:把shmid_ds結構中的數據設置爲共享內存的當前關聯值,即用共享內存的當前關聯值覆蓋shmid_ds的值。
          IPC_SET:如果進程有足夠的權限,就把共享內存的當前關聯值設置爲shmid_ds結構中給出的值
          IPC_RMID:刪除共享內存段
-------buf是一個結構指針,它指向共享內存模式和訪問權限的結構。
shmid_ds結構包括以下成員:

shmdt和shmctl的區別:
IPC_RMID 命令實際上不從內核刪除一個段,而是僅僅把這個段標記爲刪除,實際的刪除發生在最後一個進程離開這個共享段時。
shmdt(addr)使進程中的shmaddr指針無效化,不可以使用,但是保留空間。
shmctl(shmid,IPC_RMID,0) 刪除共享內存,徹底不可用,釋放空間。


創建兩個進程,在A進程中創建一個共享內存,並向其寫入數據,通過B進程從共享內存中讀取數據。此程序是子進程進行寫入,父進程進行了讀取
shm.h如下:

shm.c如下:

test_shm.c如下:

運行結果如下:


刪除共享內存

3、安全性
        這個程序是不安全的,當有多個程序同時向共享內存中讀寫數據時,問題就會出現。可能你會認爲,可以改變一下writer的使用方式,例如,只有當writer爲0時進程纔可以向共享內存寫入數據,而當一個進程只有在writer不爲0時才能對其進行讀取,同時把writer進行加1操作,讀取完後進行減1操作。這就有點像文件鎖中的讀寫鎖的功能。它似乎能行得通,但是這都不是原子操作,所以這種做法是行不能的。

4、共享內存的優缺點

1)優點:
       a、函數的接口簡單;
       b、進程間通信中速度最快的。數據的共享使進程間的數據不用傳送,而是直接訪問內存,加快了程序的效率。
       C、無侷限性,可用於任意兩個進程間的通信。它也不像匿名管道那樣要求通信的進程有一定的父子關係。
2)缺點:共享內存沒有提供同步的機制,這使得我們在使用共享內存進行進程間通信時,往往要藉助其他的手段來進行進程間的同步工作。

4、mmap和shm共享內存的區別和聯繫

       mmap()系統調用使得進程之間通過映射同一個普通文件實現共享內存。普通文件被映射到進程地址空間後,進程可以向訪問普通內存一樣對文件進行訪問,不必再調用read(),write()等操作。 成功執行時,mmap()返回被映射區的指針,munmap()返回0。失敗時,mmap()返回MAP_FAILED[其值爲(void *)-1],munmap返回-1。
mmap和shm的機制
      mmap的機制:就是在磁盤上建立一個文件,每個進程存儲器裏面,單獨開闢一個空間來進行映射。如果多進程的話,那麼不會對實際的物理存儲器(主存)消耗太大。
       shm的機制:每個進程的共享內存都直接映射到實際物理存儲器裏面。
區別:
1、mmap保存到實際硬盤,實際存儲並沒有反映到主存上。
     優點:儲存量可以很大(多於主存);缺點:進程間讀取和寫入速度要比主存的要慢。
2、shm保存到物理存儲器(主存),實際的儲存量直接反映到主存上。
      優點:進程間訪問速度(讀寫)比磁盤要快;缺點:儲存量不能非常大(多於主存)
        在使用時,如果分配的存儲量不大,那麼使用shm;如果存儲量大,那麼使用mmap。
        mmap系統調用並不是完全爲了用於共享內存而設計的。它本身提供了不同於一般對普通文件的訪問方式,進程可以像讀寫內存一樣對普通文件的操作。而Posix或系統V的共享內存IPC則純粹用於共享目的,當然mmap()實現共享內存也是其主要應用之一。
        mmap並不分配空間, 只是將文件映射到調用進程的地址空間裏, 然後你就可以用memcpy等操作寫文件, 而不用write()了.寫完後用msync()同步一下, 你所寫的內容就保存到文件裏了. 不過這種方式沒辦法增加文件的長度, 因爲要映射的長度在調用mmap()的時候就決定了。簡單說就是把一個文件的內容在內存裏面做一個映像,內存比磁盤快些。基本上它是把一個檔案對應到你的virtual memory 中的一段,並傳回一個指針。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章