mmap内存映射、system V共享内存和Posix共享内存
linux內核支持多種共享內存方式,如mmap內存映射,Posix共享內存,以system V共享內存。當內核空間和用戶空間存在大量數據交互時,共享內存映射就成了這種情況下的不二選擇。它能夠最大限度的降低內核空間和用戶空間之間的數據拷貝,從而大大提高系統的性能。
共享內存是最有用,也是最快的IPC方式。兩個不同進程A、B共享內存時,同一塊物理內存被映射到進程A、B各自的進程地址空間。進程A可以即時看到進程B對共享內存中數據的更新,反之亦然。由于多個進程共享同一塊內存區域,必然需要某種同步機制,互斥鎖和信號量都可以。
內存映射,簡而言之就是將用戶空間的一段內存區域(即進程地址空間的內存映射段,其位于堆空間和棧空間之間)映射到內核空間,映射成功后,用戶對這段內存區域的修改可以直接反映到內核空間,同樣,內核空間對這段區域的修改也直接反映用戶空間,那么對于內核空間和用戶空間兩者之間需要大量數據傳輸等操作的話效率是非常高的。對于Posix共享內存和system V共享內存一樣,在地址空間處于內存映射段,物理內存處于內核區。因此,這三種方式都不需要內核區與用戶區進行數據的交換,效率更高,通過指針的方式可以直接對內存進行訪問。對于大數據的內存訪問,一般來說在Linux系統中采用內存映射和共享內存是最好的方式,這樣對于應用層來說,可以很方便的訪問到內核的空間。
mmap內存映射
mmap內存映射的方式分為兩種:文件映射(一般文件或者/dev/zero文件)和匿名映射。文件映射,是指該地址空間(內存映射段)的內容來自于一個文件;而匿名映射地址空間背后什么靠山都沒有。進程的代碼段來自于鏡像,采用文件映射方式;而棧,堆,.bss段,數據段均是匿名映射。
下圖是文件映射的示意圖(映射文件的一部分):
off為偏移大小,len為映射區的大小。
mmap函數是unix和類unix下的系統調用,其系統調用接口為:void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);?? mmap內存映射的作用有二:1.用于共享內存,完成IPC;2.提供了不同于一般訪問文件的方式,如系統函數(read和write等)、C庫函數(printf、scanf等),由于其不需要內核空間與用戶空間的數據拷貝,因此效率會更高。使得訪問文件類似于直接訪問內存一樣,直接用指針就可以操作文件。而Posix或system V的共享內存IPC則純粹用于共享目的,當然mmap實現共享內存也是其主要應用之一。
mmap內存映射并不分配空間,只是將文件映射到調用進程的地址空間里(但是會占掉你的 virutal memory),然后你就可以用memcpy等操作寫文件,而不用write了。寫完后,內存中的內容并不會立即更新到文件中,而是有一段時間的延遲,你可以調用msync來顯式同步一下, 這樣你所寫的內容就能立即保存到文件里了。不過通過mmap來寫文件這種方式沒辦法增加文件的長度,因為要映射的長度在調用mmap的時候就決定了。
在內存映射的過程中,并沒有實際的數據拷貝,文件沒有被載入內存,只是邏輯上被放入了地址空間的內存映射段。在用指針實際訪問的時候,邏輯地址加上基地址形成的線性地址經過MMU映射成為物理地址,此時會檢測到訪問的文件不在內存,從而通過缺頁中斷請求的方式,才將其調入內存,并同時修改頁表和快表,此時才真正調入到內存。即MMU完成了地址的變換(變換后的信息存儲在PCB中)、缺頁中斷、調頁請求等。缺頁中斷的中斷響應函數會在swap中尋找相對應的頁面,如果找不到(也就是該文件從來沒有被讀入內存的情況),則會通過mmap建立的映射關系,從硬盤上將文件讀取到物理內存中。如果在拷貝數據時,發現物理內存不夠用,則會通過虛擬內存機制(swap)將暫時不用的物理頁面交換到硬盤上。
比較:mmap、C庫函數、系統函數、管道和消息隊列
用戶進程通過系統調用訪問系統資源(如訪問文件)時需要切換到內核態,而這對應一些特殊的堆棧和內存環境,必須在系統調用前建立好。在系統調用后,CPU會從內核態轉換到用戶態,而堆棧又必須恢復成用戶進程的上下文,這種切換會消耗大量的系統時間。在用戶態,不僅僅是系統資源,位于其它進程空間的資源都是看不到的(透明的),因此在切換到用戶態時,需要將系統調用訪問的資源拷貝到用戶空間,才能被用戶空間進行操作,用戶態是沒有權限對內核進行操作的。
read、write等系統函數訪問文件時,都是先將外部設備上的數據讀到內核緩沖區,然后將內核的數據交到用戶緩沖區,然后用戶進程就可以通過用戶緩沖區來使用這些數據了。printf等C庫函數一樣,它們的唯一區別就是,C庫函數有自己維護的I/O緩沖區(8KB),而系統函數需要自己指定緩沖區buff。C庫函數對文件的訪問,最終也是通過read和write等系統函數來完成的。但由于其I/O緩沖區(無緩沖、行緩沖和全緩沖)比較大,因此較少了內核與用戶的交互次數,從而一般比系統函數訪問速度會更快,但是系統函數可以通過指定buff的大小大于I/O緩沖區的大小,使其效率比C庫函數更高,它們的原理一樣的。 綜上,內核緩沖區是為了減少操作磁盤等外部設備的次數,緩解CPU的速度與外部設備速度之間的不匹配;而I/O緩沖區是為了減少系統調用的次數。
采用共享內存通信的一個顯而易見的好處是效率高,因為進程可以直接讀寫內存,而不需要任何數據的拷貝。對于像管道和消息隊列等通信方式,則需要在內核和用戶空間進行四次的數據拷貝(讀文件read、再write到管道一端、再從管道另一端read、最后再write到外部文件),而共享內存則只拷貝兩次數據。一次從輸入文件到共享內存區,另一次從共享內存區到輸出文件。實際上,進程之間在共享內存時,并不總是讀寫少量數據后就解除映射,有新的通信時,再重新建立共享內存區域。而是保持共享區域,直到通信完畢為止,這樣,數據內容一直保存在共享內存中,并沒有寫回文件。共享內存中的內容往往是在解除映射時才寫回文件的。因此,采用共享內存的通信方式效率是非常高的。
內核將硬盤文件的內容直接映射到內存, 任何應用程序都可通過Linux的mmap()系統調用請求這種映射。內存映射是一種方便高效的文件I/O方式, 因而被用于裝載動態共享庫。如C標準庫函數(fread、fwrite、fopen等)和Linux系統I/O函數,它們都是動態庫函數,其中C標準庫函數都被封裝在了/lib/libc.so庫文件中,都是二進制文件。這些動態庫函數都是與位置無關的代碼,即每次被加載進入內存映射區時的位置都是不一樣的,因此使用的是其本身的邏輯地址,經過變換成線性地址(虛擬地址),然后再映射到內存。而靜態庫不一樣,由于靜態庫被鏈接到可執行文件中,因此其位于代碼段,每次在地址空間中的位置都是固定的。
system V共享內存
共享內存就是多個進程的地址空間映射到同一個物理內存,多個進程都能看到這塊物理內存,共享內存可以提供給服務器進程和客戶進程之間進行通信,不需要進行數據的復制,所以速度最快。
共享內存操作需要的函數:
1.我們需要利用ftok函數生成key標識符。
key_t ftok(const char *pathname,int proj_id);
2.使用shmgt函數,創建一個共享內存塊,返回這個共享內存塊的標識符shmid。
int shmget(key_t key,size_t size,int shmflg);
size是需要申請的共享內存的大小,需要注意的是,操作系統為你提供的大小的時候是按頁來提供,所以size為4k的整數倍;shmflg:如果要創建新的共享內存,那么就使用IPC_CREAT,IPC_EXCL,如果是已經存在的,那么只需要使用IPC_CREAT。
3.用shmat掛接共享內存(將進程地址空間掛接到共享內存,共享內存是物理空間,可以有多個掛接)
void *shmat(int shmid,const void *shmaddr, int shmflg);
shmid是掛接的進程號;shmaddr置為NULL,讓系統選擇一個合適的地址空間進行掛接;shmflg表示什么方式進行掛接,一般都是取0;函數返回各個進程掛接的虛擬的地址空間。
4.用shmdt去掛接。
int shmdt(const void *shmaddr);
5.用shmctl銷毀共享內存
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
cmd取IPC_RMID表示刪除這塊共享內存;buf一般設置為NULL,不關心這個東西,消息隊列中也有這么一個類似的結構體也是設置為NULL。
共享內存與mmap內存映射的區別
首先說明相同點:都可以進行進程之間的通信。
mmap還可以提供非文件進行訪問的操作,訪問文件就像訪問內存一樣,可以用指針直接對文件進行操作,但是速度還是不一樣,畢竟mmap最終還是訪問的是磁盤文件。
mmap和shm:
1.mmap是在磁盤上建立一個文件,每個進程地址空間中開辟出一塊空間進行映射。而對于shm而言,shm每個進程最終會映射到同一塊物理內存。shm保存在物理內存,這樣讀寫的速度要比磁盤要快,但是存儲量不是特別大。
2.相對于shm來說,mmap更加簡單,調用更加方便,所以這也是大家都喜歡用的原因。
3.另外mmap有一個好處是當機器重啟,因為mmap把文件保存在磁盤上,這個文件還保存了操作系統同步的映像,所以mmap不會丟失,但是shmget就會丟失。
總結
以上是生活随笔為你收集整理的mmap内存映射、system V共享内存和Posix共享内存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 文件进程间通信
- 下一篇: mmap、munmap函数