内存映射(mmap系统调用)
映射虛擬內存-->物理內存/Swap/文件
?
文件映射到內存,內存訪問取代IO訪問
?
可以映射同一個文件以(進程)共享內存
???
Linux進程虛擬地址空間---(分成)-->虛擬內存區
虛擬內存區(VMA)表:進程所有的虛擬內存區
內存映射:創建一個虛擬內存區(VMA),映射文件?
???
映射文件:外部程序調用VMA的(操作函數集中)操作函數訪問映射文件的VMA?
????
??
可以通過addr參數映射使用某個特定的內存地址。如果它的取值是零,結果指針將自動分配?
???? ?
mmap函數是unix/linux下的系統調用,mmap系統調用并不是完全為了用于共享內存而設計的。它本身提供了不同于一般對普通文件的訪問方式,進程可以像讀寫內存一樣對普通文件的操作。而Posix或系統V的共享內存IPC則純粹用于共享目的,當然mmap()實現共享內存也是其主要應用之一。
????????? mmap系統調用使得進程之間通過映射同一個普通文件實現共享內存。普通文件被映射到進程地址空間后,進程可以像訪問普通內存一樣對文件進行訪問,不必再調用read(),write()等操作。mmap并不分配空間, 只是將文件映射到調用進程的地址空間里, 然后你就可以用memcpy等操作寫文件, 而不用write()了.寫完后用msync()同步一下, 你所寫的內容就保存到文件里了. 不過這種方式沒辦法增加文件的長度, 因為要映射的長度在調用mmap()的時候就決定了.
簡單說就是把一個文件的內容在內存里面做一個映像,內存比磁盤快些。
基本上它是把一個檔案對應到你的virtual memory 中的一段,并傳回一個指針。
以后對這段 memory 做存取時,其實就是對那個檔做存取。
它就是一種快速 file I/O 的東東,而且使用上和存取 memory 一樣方便,只不過會占掉你的 virutal memory。
#include <sys/types.h>
#include <sys/stat.h> //文件狀態結構
#include <unistd.h>
#include <sys/mman.h> //mmap頭文件
void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
參數:? ?
start:映射區的開始地址。
length:映射區的長度。
prot:期望的內存保護方式,不能與文件的打開模式沖突。是以下的某個值,可以通過or運算合理地組合在一起
??? PROT_EXEC //頁內容可以被執行
??? PROT_READ??//頁內容可以被讀取
??? PROT_WRITE //頁可以被寫入
??? PROT_NONE??//頁不可訪問
flags:指定映射對象的類型,映射選項和映射頁是否可以共享。它的值可以是一個或者多個以下位的組合體
??? MAP_FIXED //使用指定的映射起始地址,如果由start和len參數指定的內存區重疊于現存的映射空間,重疊部分將會被丟棄。如果指定的起始地址不可用,操作將會失敗。并且起始地址必須落在頁的邊界上。
??? MAP_SHARED //與其它所有映射這個對象的進程共享映射空間。對共享區的寫入,相當于輸出到文件。直到msync()或者munmap()被調用,文件實際上不會被更新。
??? MAP_PRIVATE //建立一個寫入時拷貝的私有映射。內存區域的寫入不會影響到原文件。這個標志和以上標志是互斥的,只能使用其中一個。
??? MAP_DENYWRITE //這個標志被忽略。
??? MAP_EXECUTABLE //同上
??? MAP_NORESERVE //不要為這個映射保留交換空間。當交換空間被保留,對映射區修改的可能會得到保證。當交換空間不被保留,同時內存不足,對映射區的修改會引起段違例信號。
??? MAP_LOCKED //鎖定映射區的頁面,從而防止頁面被交換出內存。
??? MAP_GROWSDOWN //用于堆棧,告訴內核VM系統,映射區可以向下擴展。
??? MAP_ANONYMOUS //匿名映射,映射區不與任何文件關聯。
??? MAP_ANON //MAP_ANONYMOUS的別稱,不再被使用。
??? MAP_FILE //兼容標志,被忽略。
??? MAP_32BIT //將映射區放在進程地址空間的低2GB,MAP_FIXED指定時會被忽略。當前這個標志只在x86-64平臺上得到支持。
??? MAP_POPULATE //為文件映射通過預讀的方式準備好頁表。隨后對映射區的訪問不會被頁違例阻塞。
??? MAP_NONBLOCK //僅和MAP_POPULATE一起使用時才有意義。不執行預讀,只為已存在于內存中的頁面建立頁表入口。
fd:有效的文件描述詞。如果 MAP_ANONYMOUS被設定,為了兼容問題,其值應為-1。
offset:被映射對象內容的起點。
??? 返回說明:成功執行時,mmap()返回被映射區的指針,munmap()返回0。失敗時,mmap()返回 MAP_FAILED[其值為(void *)-1],munmap返回-1。errno被設為以下的某個值? ?
??? EACCES:訪問出錯
??? EAGAIN:文件已被鎖定,或者太多的內存已被鎖定
??? EBADF:fd不是有效的文件描述詞
??? EINVAL:一個或者多個參數無效
??? ENFILE:已達到系統對打開文件的限制
??? ENODEV:指定文件所在的文件系統不支持內存映射
??? ENOMEM:內存不足,或者進程已超出最大內存映射數量
??? EPERM:權能不足,操作不允許
??? ETXTBSY:已寫的方式打開文件,同時指定MAP_DENYWRITE標志
??? SIGSEGV:試著向只讀區寫入
??? SIGBUS:試著訪問不屬于進程的內存區
??
你可以通過addr參數請求使用某個特定的內存地址。如果它的取值是零,結果指針將將自動分配。這是推薦的做法,否則會降低程序的可移植性,因為不同系統上的可用地址范圍是不一樣的。你可以通過addr參數請求使用某個特定的內存地址。如果它的取值是零,結果指針將將自動分配。這是推薦的做法,否則會降低程序的可移植性,因為不同系統上的可用地址范圍是不一樣的。
??
int munmap(void *start, size_t length);
int msync(const void *start, size_t length, int flags);
如果開啟記憶體對映是希望寫入檔案中,那麼修改過的記憶體會在一段時間內與檔案稍稍有點不同。如果您希望立即將資料寫入檔案中,可使用msync。
start為記憶體開始位置,length為長度。
flags則有三個:
MS_ASYNC : 請Kernel快將資料寫入。
MS_SYNC : 在msync結束返回前,將資料寫入。
MS_INVALIDATE : 讓核心自行決定是否寫入,僅在特殊狀況下使用
例子:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
int main()
{
?int fd;
?if ( (fd = open("./file", O_RDWR|O_CREAT, S_IRWXU)) < 0){
? printf("open file wrong!");
? exit(1);
?}
?
?struct stat file_stat;
?if ( fstat( fd, &file_stat) < 0 )
?{
? printf(" fstat wrong");
? exit(1);
?}
?void *start_fp;
?if( ( start_fp = mmap(NULL, file_stat.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 )) == MAP_FAILED)
?{
? printf("mmap wrong");
? exit(0);
?}
? snprintf( (char *)start_fp, 4, "test");
? msync( start_fp, file_stat.st_size, MS_ASYNC);
? if ( munmap( start_fp, file_stat.st_size ) < 0 )
? {
? printf("munmap wrong");
? exit(1);
? }
}
mmap這個系統調用可以直接對底層的操作,映射硬件地址,實現用戶層驅動,但是它的實現方式是怎樣對底層操作的呢?
首先看個簡單的對i/o操作的程序:
#include <sys/types.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <memory.h>
#define DEVICE_FILE_NAME "at91_gpio"
#define????AT91_SYS????????? 0xfffff000
#define????AT91_SYS_2??????? 0xfffc0000
#define??? AT91C_PIOB_PER??? 0x600
#define????AT91C_PIOB_OER??? 0x610
#define??? AT91C_PIOC_CODR?? 0x634
#define????AT91C_PIOC_SODR?? 0x630
#define??? AT91C_PIOA_ASR????0x470
#define??? AT91C_PIOA_PDR????0x404
#define????AT91C_PIOA_PSR????0x408
#define????AT91C_PIOA_ODR????0x414
#define????AT91C_PIOA_IFDR???0x424
#define????AT91C_PIOA_CODR???0x434
#define????AT91C_PIOA_IDR????0x444
#define????AT91C_PIOA_MDDR???0x454
#define????AT91C_PIOA_PUER???0x464
#define????AT91C_PIOA_OWDR???0x4A4
#define????AT91C_PIOA_ABSR???0x478
#define INT *(volatile unsigned int*)
void * map_base;
FILE *f;
int n,fd;
int main(int argc,char *argv[])
{
????if((fd=open("/dev/mem",O_RDWR|O_SYNC))==-1){
????return(-1);
????}
????map_base=mmap(0,0xff,PROT_READ|PROT_WRITE,MAP_SHARED,/
?????????????????? fd,AT91_SYS);
????INT (map_base+AT91C_PIOB_PER)=(1<<27);//使能寄存器
????INT (map_base+AT91C_PIOB_OER)=(1<<27);//輸出使能寄存器
????while(1){
????????INT (map_base+AT91C_PIOB_CODR)=(1<<27);//IO口置高
????????sleep(1);
????????INT (map_base+AT91C_PIOB_SODR)=(1<<27);//IO口置低
????????sleep(1);
????}
????close(fd);
?
??? munmap(map_base,0xff);//解除映射關系
}
在這個程序中,首先打開了/dev/mem,然后進行了mmap,也就是對整個物理內存的映射。接下來的操作就根據芯片手冊,找到相應的物理地址,對物理地址直接操作。
其中有幾個參數需要說明:
PROT_READ:區域可讀;
PROT_WRITE:區域可寫;
MAP_SHARED:對映射區域的寫入數據會復制回文件內, 而且允許其他映射該文件的進程共享。
?
書中對此圖有些說明:在此圖中,“起始地址”是mmap的返回值。在圖中,映射存儲區位于堆和棧之間:這屬于實現細節,各種實現之間可能不同。
?
看了此圖以后,大家對mmap的實現方式可能就清楚多了,應該對上面那個程序有更多的理解了吧。我們把物理地址都映射到內存中,然后對內存中的地址進行操作,通過MAP_SHARED參數,對映射區域的寫入數據復制回文件內,從而達到了對IO口的操作。對于MAP_SHARED區磁盤文件的更新,在寫到存儲映射區時按內核虛存算法自動進行。
還有一點需要說明,因為映射文件的起動位移量受系統虛存頁長度的限制,那么如果映射區的長度不是頁長度的整數倍時,將如何呢?假定文件長12字節,系統頁長為512字節,則系統通常提供512字節的映射區,其中后500字節被設為0。可以修改這 500字節,但任何變動都不會在文件中反映出來。
?
用戶空間驅動的好處在于:
?
-
完整的 C 庫可以連接. 驅動可以進行許多奇怪的任務, 不用依靠外面的程序(實現使用策略的工具程序, 常常隨著驅動自身發布).
-
程序員可以在驅動代碼上運行常用的調試器, 而不必走調試一個運行中的內核的彎路.
-
如果一個用戶空間驅動掛起了, 你可簡單地殺掉它. 驅動的問題不可能掛起整個系統, 除非被控制的硬件真的瘋掉了.
-
用戶內存是可交換的, 不象內核內存. 一個不常使用的卻有很大一個驅動的設備不會占據別的程序可以用到的 RAM, 除了在它實際在用時.
-
一個精心設計的驅動程序仍然可以, 如同內核空間驅動, 允許對設備的并行存取.
-
如果你必須編寫一個封閉源碼的驅動, 用戶空間的選項使你容易避免不明朗的許可的情況和改變的內核接口帶來的問題.
但是用戶空間的設備驅動的方法有幾個缺點. 最重要的是:
?
-
中斷在用戶空間無法用. 在某些平臺上有對這個限制的解決方法, 例如在 IA32 體系上的 vm86 系統調用.
-
只可能通過內存映射 /dev/mem 來使用 DMA, 而且只有特權用戶可以這樣做.
-
存取 I/O 端口只能在調用 ioperm 或者 iopl 之后. 此外, 不是所有的平臺支持這些系統調用, 而存取/dev/port可能太慢而無效率. 這些系統調用和設備文件都要求特權用戶.
-
響應時間慢, 因為需要上下文切換在客戶和硬件之間傳遞信息或動作.
-
更不好的是, 如果驅動已被交換到硬盤, 響應時間會長到不可接受. 使用 mlock 系統調用可能會有幫助, 但是常常的你將需要鎖住許多內存頁, 因為一個用戶空間程序依賴大量的庫代碼. mlock, 也, 限制在授權用戶上.
-
最重要的設備不能在用戶空間處理, 包括但不限于, 網絡接口和塊設備.
如你所見, 用戶空間驅動不能做的事情畢竟太多. 感興趣的應用程序還是存在: 例如, 對 SCSI 掃描器設備的支持( 由 SANE 包實現 )和 CD 刻錄器 ( 由 cdrecord 和別的工具實現 ). 在兩種情況下, 用戶級別的設備情況依賴 "SCSI gneric" 內核驅動, 它輸出了低層的 SCSI 功能給用戶程序, 因此它們可以驅動它們自己的硬件.
一種在用戶空間工作的情況可能是有意義的, 當你開始處理新的沒有用過的硬件時. 這樣你可以學習去管理你的硬件, 不必擔心掛起整個系統. 一旦你完成了, 在一個內核模塊中封裝軟件就會是一個簡單操作了,由于用戶空間的諸多限制,迫使我們不得不進行內核空間的設備驅動開發。不過在我的項目中,用mmap就可以解決所有問題了。
總結
以上是生活随笔為你收集整理的内存映射(mmap系统调用)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从open系统调用的源码看文件的打开过程
- 下一篇: ELF文件的格式和加载过程