linux儲存 共享記憶體機制shm(十三)

2021-10-07 16:50:58 字數 4318 閱讀 2795

實現程序間通訊最簡單也是最直接的方法就是共享記憶體——為參與通訊的多個程序在記憶體中開闢乙個共享區。由於程序可以直接對共享記憶體進行讀寫操作,因此這種通訊方式效率特別高,但其弱點是,它沒有互斥機制,需要訊號量之類的手段來配合。

共享記憶體,顧名思義,就是兩個或多個程序都可以訪問的同一塊記憶體空間,乙個程序對這塊空間內容的修改可為其他參與通訊的程序所看到的。

顯然,為了達到這個目的,就需要做兩件事:一件是在記憶體劃出一塊區域來作為共享區;另一件是把這個區域對映到參與通訊的各個程序空間。

通常在記憶體劃出乙個區域的方法是,在記憶體中開啟乙個檔案,若通過系統呼叫mmap()把這個檔案所占用的記憶體空間對映到參與通訊的各個程序位址空間,則這些程序就都可以看到這個共享區域,進而實現程序間的通訊。

為了方便,再把mmap()的原理簡述如下:

mmap()原型如下:

void

*mmap

(void

*start, size_t len,

int prot,

int flags,

int fd, off_t offset)

;

其中,引數fd用來指定被對映的檔案;offset指定對映的起始位置偏移量(通常為0);len指定檔案被對映部分的長度;start用來指定對映到虛位址空間的起始位置(通常為null,即由系統確定)。

mmap是一種記憶體對映檔案的方法,即將乙個檔案或者其它物件對映到程序的位址空間,實現檔案磁碟位址和程序虛擬位址空間中一段虛擬位址的一一對映關係。實現這樣的對映關係後,程序就可以採用指標的方式讀寫操作這一段記憶體,而系統會自動回寫髒頁面到對應的檔案磁碟上,即完成了對檔案的操作而不必再呼叫read,write等系統呼叫函式。相反,核心空間對這段區域的修改也直接反映使用者空間,從而可以實現不同程序間的檔案共享。

mmap()對映過程示意圖如下所示:

那麼mmap是怎麼形成這個檔案對映過程呢?

mmap本身其實是乙個很簡單的操作,在程序頁表中新增乙個頁表項,該頁表項是物理記憶體的位址。呼叫mmap的時候,核心會在該程序的位址空間的對映區域查詢一塊滿足需求的空間用於對映該檔案,然後生成該虛擬位址的頁表項,改頁表項此時的有效位(標誌是否已經在物理記憶體中)為0,頁表項的內容是檔案的磁碟位址,此時mmap的任務已經完成。

簡而言之,就是在程序對應的虛存段新增乙個段,也就是建立乙個新的vm_area_struct結構,並將其與檔案的物理磁碟位址相連。在建立虛擬區間並完成位址對映,但是並沒有將任何檔案資料的拷貝至主存。真正的檔案讀取是當程序發起讀或寫操作時。程序的讀或寫操作訪問虛擬位址空間這一段對映位址,通過查詢頁表,引發缺頁異常,核心進行請頁。

ipc的共享記憶體通訊方式與上面的mmap()方式極為相似,但因為建立乙個檔案的目的僅是為了通訊,於是這種檔案沒有永久儲存的意義,因此ipc並沒有使用正規的檔案系統,而是在系統初始化時在磁碟交換區建立了乙個專門用來實現共享記憶體的特殊臨時檔案系統shm,當系統斷電後,其中的檔案會全部自行銷毀。

linux的乙個共享記憶體區由多個共享段組成。用來描述共享記憶體段的核心資料結構shmid_kernel如下:

struct shmid_kernel /* private to the kernel */

;

shmid_kernel中最重要的域是指標shm_file,它指向臨時檔案file物件。當程序需要使用這個檔案進行通訊時,由核心負責將其對映到使用者位址空間。

為了便於管理,核心把共享記憶體區的所有描述結構shmid_kernel都存放在結構ipc_id_ary中的乙個陣列中。結構ipc_id_ary的定義如下:

struct ipc_id_ary

;

同樣,為了描述乙個共享記憶體區的概貌,核心使用了資料結構ipc_ids。該結構的定義如下:

struct ipc_ids 

;

由多個共享段組成的共享區的結構如下所示:

標頭檔案:

#include

程序可以通過呼叫函式shmget()來開啟或建立乙個共享記憶體區。函式shmget()內部由系統呼叫sys_shmget來實現。函式shmget()的原型如下:

int

shmget

(key_t key, size_t size,

int flag)

;

其中,引數key為使用者給定的鍵值。

所謂的鍵值,是在ipc的通訊模式下每個ipc物件的名字。程序通過鍵值識別所有的物件。如果不使用鍵,程序將無法獲取ipc物件,因此ipc物件並不存在於程序本身所使用的的記憶體中。

因此任何程序都無法為一塊共享記憶體定義乙個鍵值。因此,在呼叫函式shmget()時,需要key設為ipc_private,這樣,作業系統將忽略鍵,建立乙個新的共享記憶體,指定乙個鍵值並返回這塊共享記憶體的ipc識別符號id,然後再設法將這個新的共享記憶體的識別符號id告訴其他需要使用這個共享記憶體區的程序。

函式中的引數size為所申請的共享儲存段的長度(以頁為單位)。

函式中的引數flag為標誌,常用的有效標誌有ipc_creat和ipc_excl,它們的功能與檔案開啟函式open()的o_creat和o_excl相當。如果使用者希望所建立的共享記憶體區可讀,則需要使用標誌s_irusr;若可讀,則需要使用標誌s_iwusr。

函式shmget()呼叫成功後,返回共享記憶體區的id,否則返回-1。

linux用shmid_ds資料結構表示每個新建的共享記憶體。當shmget()建立一塊新的共享記憶體後,返回乙個可以引用該共享記憶體的shmid_ds資料結構的識別符號。定義在include/linux/shm.**件中的shmid_ds如下:

struct shmid_ds 

;

例如:呼叫函式shmget()為當前程序建立乙個共享記憶體區。

**如下:

int

main

(void

)else

printf

("shmget success!");

return0;

}

如果乙個程序已建立或開啟乙個共享記憶體,則在需要使用它時,要呼叫函式shmat()把該共享記憶體連線到程序上,即要把待使用的共享記憶體對映到程序空間。函式shmat()通過系統呼叫sys_shmat()實現。函式shmat()的原型如下:

void

*shmat

(int shmid,

char __user * shmaddr,

int shm***)

;

其中,引數shmid為共享記憶體的標識;引數shmaddr為對映位址,如果該值為0,則由核心決定;引數shm***為共享記憶體的標誌,如果shm***的值為shm_rdonly,則程序以唯讀的方式訪問共享記憶體,否則以讀寫方式訪問共享記憶體。

若函式呼叫成功,則返回共享儲存段位址;若出錯,則返回-1。

呼叫函式shmdt()可以斷開共享記憶體與程序的連線,其原型如下:

int

shmdt

(coid * addr)

;

呼叫函式shmctl()可以對共享記憶體進行一些控制,其原型如下:

int

shmctl

(int shmid,

int cmd,

struct shmid_ds * buf)

;

其中,引數shmid為共享儲存段的id;引數cmd為控制命令,常用的值有ipc_stat(賦值)、ipc_set(賦值)、ipc_rmid(刪除)、shm_lock(上鎖)、shm_unlock(解鎖)等等;引數buf為struct shmid_ds型別指標,由buf返回的數值與命令引數cmd表示的操作相關。

共享記憶體不會隨著程式的結束而自動消除,要麼呼叫shmctl()刪除,要麼手動使用命令ipcrm -m shmid去刪除,否則一直保留在系統中,直至系統掉電。

例子:呼叫函式shmget()為當前程序建立乙個共享記憶體區並使用它。

**如下:

#include

#include

#include

#include

intmain

(void

)

從上面的敘述中可以看到,共享記憶體是一種低階的通訊機制,它沒有提供程序間同步和互斥的功能。所以,共享記憶體通常是要與訊號量結合使用。

共享記憶體機制

共享記憶體主要是通過對映機制實現的。共享記憶體例項一 include include include include using namespace std int main else getchar 注意,程序關閉後,所有控制代碼自動關閉,所以要在這裡暫停 解除檔案對映,關閉記憶體對映檔案物件控制...

linux ipc機制 共享記憶體

17.shmat 功能 聯接共享記憶體的操作.語法 include include include void shmat shmid,shmaddr,shm int shmid void shmaddr int shmid 說明 將由shmid指示的共享記憶體聯接到呼叫程序的資料段中.被聯接的段放在...

WindowsIPC機制 共享記憶體

程序間通訊的方法有很多種,共享記憶體只是其中的一種,在這裡分享一下自己的思路,以及寫過的測試 在實現共享記憶體用到的關鍵winapi是以下幾個 mapviewoffile unmapviewoffile handle hfile,handle to file lpsecurity attribute...