天天看點

mmap - 使用者空間與核心空間

mmap概述

共享記憶體可以說是最有用的程序間通信方式,也是最快的IPC形式, 因為程序可以直接讀寫記憶體,而不需要任何資料的拷貝。對于像管道和消息隊列等通信方式,則需要在核心和使用者空間進行四次的資料拷貝,而共享記憶體則隻拷貝兩次資料: 一次從輸入檔案到共享記憶體區,另一次從共享記憶體區到輸出檔案。實際上,程序之間在共享記憶體時,并不總是讀寫少量資料後就解除映射,有新的通信時,再重建立立共享記憶體區域。而是保持共享區域,直到通信完畢為止,這樣,資料内容一直儲存在共享記憶體中,并沒有寫回檔案。共享記憶體中的内容往往是在解除映射時才寫回檔案的。是以,采用共享記憶體的通信方式效率是非常高的。

傳統檔案通路

UNIX通路檔案的傳統方法是用open打開它們, 如果有多個程序通路同一個檔案, 則每一個程序在自己的位址空間都包含有該檔案的副本,這不必要地浪費了存儲空間. 下圖說明了兩個程序同時讀一個檔案的同一頁的情形. 系統要将該頁從磁盤讀到高速緩沖區中, 每個程序再執行一個存儲器内的複制操作将資料從高速緩沖區讀到自己的位址空間.

mmap - 使用者空間與核心空間

共享存儲映射

現在考慮另一種處理方法: 程序A和程序B都将該頁映射到自己的位址空間, 當程序A第一次通路該頁中的資料時, 它生成一個缺頁中斷. 核心此時讀入這一頁到記憶體并更新頁表使之指向它.以後, 當程序B通路同一頁面而出現缺頁中斷時, 該頁已經在記憶體, 核心隻需要将程序B的頁表登記項指向次頁即可. 如下圖所示:

mmap - 使用者空間與核心空間

mmap系統調用使得程序之間通過映射同一個普通檔案實作共享記憶體,普通檔案被映射到程序位址空間後,程序可以像通路普通記憶體一樣對檔案進行通路,不必再調用read和write等。

mmap - 使用者空間與核心空間

mmap使用者空間

頭檔案 sys/mman.h

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

int munmap(void *start, size_t length);

int msync ( void * addr , size_t len, int flags) 通過調用msync()實作磁盤上檔案内容與共享記憶體區的内容一緻

作用:

mmap将一個檔案或者其他對象映射進記憶體,當檔案映射到程序後,就可以直接操作這段虛拟位址進行檔案的讀寫等操作。

參數說明:

start:映射區的開始位址

length:映射區的長度

prot:期望的記憶體保護标志

—-PROT_EXEC //頁内容可以被執行

—-PROT_READ //頁内容可以被讀取

—-PROT_WRITE //頁可以被寫入

—-PROT_NONE //頁不可通路

flags:指定映射對象的類型

—-MAP_FIXED

—-MAP_SHARED 與其它所有映射這個對象的程序共享映射空間

—-MAP_PRIVATE 建立一個寫入時拷貝的私有映射。記憶體區域的寫入不會影響到原檔案

—-MAP_ANONYMOUS 匿名映射,映射區不與任何檔案關聯

fd:如果MAP_ANONYMOUS被設定,為了相容問題,其值應為-1

offset:被映射對象内容的起點

系統調用mmap可以将檔案映射至記憶體(程序空間),如此可以把對檔案的操作轉為對記憶體的操作,以此避免更多的lseek()、read()、write()等系統調用,這點對于大檔案或者頻繁通路的檔案尤其有用,提高了I/O效率。

下面例子中測試所需的data.txt檔案内容如下:

兩個程式映射同一個檔案到自己的位址空間, 程序A先運作, 每隔兩秒讀取映射區域, 看是否發生變化.

程序B後運作, 它修改映射區域, 然後推出, 此時程序A能夠觀察到存儲映射區的變化

程序A的代碼:

程序B的代碼:

linux采用的是頁式管理機制。對于用mmap()映射普通檔案來說,程序會在自己的位址空間新增一塊空間,空間大

小由mmap()的len參數指定,注意,程序并不一定能夠對全部新增空間都能進行有效通路。程序能夠通路的有效位址大小取決于檔案被映射部分的大小。簡單的說,能夠容納檔案被映射部分大小的最少頁面個數決定了程序從mmap()傳回的位址開始,能夠有效通路的位址空間大小。超過這個空間大小,核心會根據超過的嚴重程度傳回發送不同的信号給程序。可用如下圖示說明:

mmap - 使用者空間與核心空間

總結一下就是, 檔案大小, mmap的參數 len 都不能決定程序能通路的大小, 而是容納檔案被映射部分的最小頁面數決定程序能通路的大小. 下面看一個執行個體:

mmap核心空間

核心空間的mmap函數原型為:int (*map)(struct file *filp, struct vm_area_struct *vma);

作用是實作使用者程序中的位址與核心中實體頁面的映射

核心空間mmap函數具體實作步驟如下:

1. 通過kmalloc, get_free_pages, vmalloc等配置設定一段虛拟位址

2. 如果是使用kmalloc, get_free_pages配置設定的虛拟位址,那麼使用virt_to_phys()将其轉化為實體位址,再将得到的實體位址通過”phys>>PAGE_SHIFT”擷取其對應的實體頁面幀号。或者直接使用virt_to_page從虛拟位址擷取得到對應的實體頁面幀号。

如果是使用vmalloc配置設定的虛拟位址,那麼使用vmalloc_to_pfn擷取虛拟位址對應的實體頁面的幀号。

3. 對每個頁面調用SetPageReserved()标記為保留才可以。

4. 通過remap_pfn_range為實體頁面的幀号建立頁表,并映射到使用者空間。

說明:kmalloc, get_free_pages, vmalloc配置設定的實體記憶體頁面最好還是不要用remap_pfn_range,建議使用VMA的nopage方法。

說明:

若共享小塊連續記憶體,上面所說的get_free_pages就可以配置設定多達幾M的連續空間,

若共享大塊連續記憶體,就得靠uboot幫忙,給linux kernel傳遞參數的時候指定”mem=”,然後在核心中使用下面兩個函數來預留和釋放記憶體。

void *alloc_bootmem(unsigned long size);

void free_bootmem(unsigned long addr, unsigned long size);

在字元裝置驅動中,有一個struct file_operation結構提,其中fops->mmap指向你自己的mmap鈎子函數,使用者空間對一個字元裝置檔案進行mmap系統調用後,最終會調用驅動子產品裡的mmap鈎子函數。在mmap鈎子函數中需要調用下面這個API:

在mmap鈎子函數中,像下面這樣就可以了

來看一個例子:

核心空間代碼mymap.c

使用者空間代碼mymap_app.c

mmap - 使用者空間與核心空間

從上面這張圖可以看出:

當系統開機,driver起來的時候會将記憶體前10個位元組初始化,通過cat /sys/devices/virtual/misc/mymap/rng_current,可以看出此時記憶體中的值。

當執行mymap_app時會将前10個位元組的内容加上10再寫進記憶體,再通過cat /sys/devices/virtual/misc/mymap/rng_current,可以看出修改後的記憶體中的值。

參考文章

linux 記憶體映射 remap_pfn_range操作

mmap詳解

資源下載下傳

mmap核心驅動與應用程式

繼續閱讀