① linux將設備地址映射到用戶空間內存映射與VMA
一般情況下,用戶空間是不可能也不應該直接訪問設備的,但是,設備驅動程序中可實現mmap ()函數,這個函數可使得用戶空間能直接訪問設備的物理地址。實際上,mmap ()實現了這樣的一個映射過程:它將用戶空間的一段內存與設備內存關聯,當用戶訪問用戶空間的這段地址范圍時,實際上會轉化為對設備的訪問。
這種能力對於顯示適配器一類的設備非常有意義,如果用戶空間可直接通過內存映射訪問顯存的話,屏幕幀的各點像素將不再需要一個從用戶空間到內核空間的復制的過程。
mmap ()必須以PAGE_SIZE為單位進行映射,實際上,內存只能以頁為單位進行映射,若要映射非PAGE_SIZE整數倍的地址范圍,要先進行頁對齊,強行以PAGE_SIZE的倍數大小進行映射。
從file_operations文件操作結構體可以看出,驅動中mmap ()函數的原型如下:
int ( *mmap)(struct file *, struct vm_area_struct* ) ;
驅動中的mmap () 函數將在用戶進行mmap ()系統調用時最終被調用,mmap ()系統調用的原型與file_operations中mmap ()的原型區別很大,如下所示:
caddr_t mmap (caddr_t addr,size_t len,int prot,int flags,int fd,off_t offset);
參數fd為文件描述符,一般由open ()返回,fd也可以指定為-1,此時需指定flags參數中的MAP_ANON,表明進行的是匿名映射。
len是映射到調用用戶空間的位元組數,它從被映射文件開頭offset個位元組開始算起,offset參數一般設為0,表示從文件頭開始映射。
prot參數指定訪問許可權,可取如下幾個值的「或」:PROT_READ(可讀)、PROT_WRITE(可寫)、PROT_EXEC(可執行)和PROT_NONE(不可訪問)。
參數addr指定文件應被映射到用戶空間的起始地址,一般被指定為NULL,這樣,選擇起始地址的任務將由內核完成,而函數的返回值就是映射到用戶空間的地址。其類型caddr_t實際上就是void*。
當用戶調用mmap ())的時候,內核會進行如下處理。
1)在進程的虛擬空間查找一塊VMA。
2)將這塊VMA進行映射。
3)如果設備驅動程序或者文件系統的file_operations定義了mmap ()操作,則調用它。
4)將這個VMA插入進程的VMA鏈表中。
file_operations中mmap ()函數的第一個參數就是步驟1)找到的VMA。
由mmap ()系統調用映射的內存可由munmap ()解除映射,這個函數的原型如下:
int munmap(caddr_t addr, size_t len ) ;
驅動程序中mmap ()的實現機制是建立頁表,並填充VMA結構體中vm_operations_struct指針。
② 預留一塊16m的內存,在函數a中通過mmap映射預留的內存,並運行會導致溢出嗎
mmap函數是unix/linux下的系統調用。
當存在客戶-服務程序中復制文件時候,其數據流如下,要經歷四次數據復制,開銷很大。
-300x93.png
如果採用共享內存的方式,那麼將大大優化IO操作,數據流變成了如下,數據只復制兩次:
-300x108.png
映射文件或設備到內存中,取消映射就是munmap函數。
語法如下:
*mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *addr, size_t length);
該函數主要用途有三個:
l  將普通文件映射到內存中,通常在需要對文件進行頻繁讀寫時使用,用內存讀寫取代I/O讀寫,以獲得較高的性能;
l  將特殊文件進行匿名內存映射,為關聯進程提供共享內存空間;
l  為無關聯的進程間的Posix共享內存(SystemV的共享內存操作是shmget/shmat)
我們來看下函數的入參選擇:
ÃÂ 參數addr:
指向欲映射的內存起始地址,通常設為 NULL,代表讓系統自動選定地址,映射成功後返回該地址。
ÃÂ 參數length:
代表將文件中多大的部分映射到內存。
ÃÂ 參數prot:
映射區域的保護方式。可以為以下幾種方式的組合:
PROT_EXEC 映射區域可被執行
PROT_READ 映射區域可被讀取
PROT_WRITE 映射區域可被寫入
PROT_NONE 映射區域不能存取
ÃÂ 參數flags:
影響映射區域的各種特性。在調用mmap()時必須要指定MAP_SHARED 或MAP_PRIVATE。
MAP_FIXED 如果參數start所指的地址無法成功建立映射時,則放棄映射,不對地址做修正。通常不鼓勵用此。
MAP_SHARED對映射區域的寫入數據會復制迴文件內,而且允許其他映射該文件的進程共享。
MAP_PRIVATE 對映射區域的寫入操作會產生一個映射文件的復制,即私人的「寫入時復制」( on write)對此區域作的任何修改都不會寫回原來的文件內容。
MAP_ANONYMOUS建立匿名映射。此時會忽略參數fd,不涉及文件,而且映射區域無法和其他進程共享。
MAP_DENYWRITE只允許對映射區域的寫入操作,其他對文件直接寫入的操作將會被拒絕。
MAP_LOCKED 將映射區域鎖定住,這表示該區域不會被置換(swap)。
ÃÂ 參數fd:
要映射到內存中的文件描述符。如果使用匿名內存映射時,即flags中設置了MAP_ANONYMOUS,fd設為-1。
ÃÂ 參數offset:
文件映射的偏移量,通常設置為0,代表從文件最前方開始對應,offset必須是分頁大小的整數倍。如下圖內存映射文件的示例。
.png
1.1.1.1 共享映射
修改共享內存中的文件內容:
#include
#include
#include
#include
#include
#include
#include
int
main (int argc, char **argv)
{
int fd, nread, i;
struct stat sb;
char *mapped;
if ( argc <= 1 )
{
printf("%s: Need file path! \n",argv[0]);
exit(-1);
}
/* 打開文件 */
if ((fd = open (argv[1], O_RDWR)) < 0)
{
perror ("open");
}
/* 獲取文件的屬性 */
if ((fstat (fd, &sb)) == -1)
{
perror ("fstat");
}
/* 將文件映射至進程的地址空間 */
if ((mapped = (char *) mmap (NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == (void *) -1)
{
perror ("mmap");
}
/* 映射完後, 關閉文件也可以操縱內存 */
close (fd);
printf ("%s", mapped);
/* 修改一個字元,同步到磁碟文件 */
mapped[0] = '0';
if ((msync ((void *) mapped, sb.st_size, MS_SYNC)) == -1)
{
perror ("msync");
}
/* 釋放存儲映射區 */
if ((munmap ((void *) mapped, sb.st_size)) == -1)
{
perror ("munmap");
}
return 0;
}
編譯後,即可執行,參數為當前目錄的一個文件名字。
例如在當前目錄下創建一個hello.txt,存入hello單詞,然後執行./a.out hello.txt,完畢後發現文件中的第一個字母h變成了0。程序將文件映射到了內存中,並將第一個字元進行了修改並同步到了磁碟文件hello.txt中。
1.1.1.2 父子進程通信
使用fork創建進程,父子進程分別往共享內存中寫入各自字元串,並讀出。
#include
#include
#include
#include
#define BUF_SIZE 100
int
main (int argc, char **argv)
{
char *p_map;
/* 匿名映射,創建一塊內存供父子進程通信 */
p_map = (char *) mmap (NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (fork () == 0)
{
sleep (1);
printf ("child got a message: %s\n", p_map);
sprintf (p_map, "%s", "from u son");
munmap (p_map, BUF_SIZE); //實際上,進程終止時,會自動解除映射。
exit (0);
}
sprintf (p_map, "%s", "from u father");
sleep (2);
printf ("parent got a message: %s\n", p_map);
return 0;
}
1.1.1.3 內存訪問溢出
linux採用的是頁式管理機制,使用mmap()映射普通文件後,進程會在自己的地址空間新增一塊空間,空間大小由mmap()的len參數指定。但是,進程並不一定能夠對全部新增空間都能進行有效訪問。進程能夠訪問的有效地址大小取決於文件被映射部分的大小。決定進程能訪問的大小是容納文件被映射部分的最小頁面數。如下圖。
.png
#include
#include
#include
#include
#include
#include
int
main (int argc, char **argv)
{
int fd, i;
int pagesize, offset;
char *p_map;
struct stat sb;
/* 取得page size */
pagesize = sysconf (_SC_PAGESIZE);
printf ("pagesize is %d\n", pagesize);
/* 打開文件 */
fd = open (argv[1], O_RDWR, 00777);
fstat (fd, &sb);
printf ("file size is %zd\n", (size_t) sb.st_size);
offset = 0;
p_map = (char *) mmap (NULL, pagesize *2, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, offset);
close (fd);
// p_map[sb.st_size] = '9'; /* 導致匯流排錯誤 */
p_map[pagesize+1] = '9'; /* 導致段錯誤 */
munmap (p_map, pagesize * 2);
return 0;
③ Linux關於地址空間和MMAP映射有何特點
Linux採用
虛擬
內存技術,系統中的所有進程之間以虛擬方式共享內存。對每個進程來說,它們好像都可以訪問整個系統的所有物理內存。更重要的是,即使單獨一個進程,它擁有的地址空間也可以遠遠大於系統物理內存。
進程地址空間由每個進程中的線性地址區組成,每個進程都有一個32位或64位的平坦(flat)空間,空間的具體大小取決於體系結構。「平坦」指地址空間范圍是一個獨立的連續區間。通常情況下,每個進程都有唯一的這種平坦空間,而且每個進程的地址空間之間彼此互不相干。兩個不同的進程可以在它們各自地址空間的相同地址內存存放不同的數據。但是進程之間也可以選擇共享地址空間,我們稱這樣的進程為線程。
在地址空間中,我們更為關心的是進程有權訪問的虛擬內存地址區間,比如08048000~0804c000。這些可被訪問的合法地址區間被成為內存區域(memory area),通過內核,進程可以給自己的地址空間動態地添加或減少內存區域。
進程只能訪問有效范圍內的內存地址。每個內存區域也具有相應進程必須遵循的特定訪問屬性,如只讀、只寫、可執行等屬性。如果一個進程訪問了不在有效范圍中的地址,或以不正確的方式訪問有效地址,那麼內核就會終止該進程,並返回「段錯誤」信息。
?
內存區域可以包含各種內存對象,如下:
?
可執行文件代碼的內存映射,成為代碼段(text section)。
?
可執行文件的已初始化全局變數的內存映射,成為數據段(data section)。
?
包含未初始化全局變數的零頁(也就是bss段)的內存映射。零頁是指頁面中的數據全部為0。
?
用於進程用戶空間棧的零頁的內存映射。
?
每一個諸如C庫或動態鏈接程序等共享庫的代碼段、數據段和bss也會被載入進程的地址空間。
?
任何內存映射文件。
?
任何共享內存段。
?
任何匿名的內存映射,比如由malloc()分配的內存。
進程地址空間的任何有效地址都只能位於唯一的區域,這些內存區域不能相互覆蓋。可以看到,在執行的進程中,每個不同的內存片斷都對應一個獨立的內存區域:棧、對象代碼、全局變數、被映射的文件等等。
內核使用內存描述符表示進程的地址空間。內存描述符由mm_struct結構體表示,定義在文件中,該結構包含了和進程地址空間有關的全部信息。
VMA
內存區域由vm_area_struct結構體描述,定義在文件中,內存區域在內核中也經常被稱作虛擬內存區域或者VMA。
VMA標志是一種位標志,它定義在vm_area_struct結構中(該結構中的vm_flags子域)。和物理頁的訪問許可權不同,VMA標志反映了內核處理頁面索需要遵守的行為准則,而不是硬體要求。VM_IO標志內存區域中包含對設備I/O空間的映射。該標志通常在設備驅動程序執行 mmap()函數進行I/O空間映射時才被設置,同時該標志也表示該內存區域不能被包含在任何進程的存放轉存(core mp)中。VM_RESERVED標志內存區域不能被換出,它也是在設備驅動程序進行映射時被設置。
vm_area_struct結構體中的vm_ops域指向與指定內存區域相關的操作函數表,內核使用表中的方法操作VMA。
mmap()和do_mmap():創建地址區間
內核使用do_mmap()函數創建一個新的線性地址區間。但是說給函數創建一個新VMA並不非常准確,因為如果創建的地址區間和一個已經存在的地址區間相鄰,並且它們具有相同的訪問許可權的話,那麼兩個區間將合並為一個。如果不能合並,那麼就確實需要創建一個新的VMA了。但無論哪種情況,do_mmap()函數都會將一個地址區間加入到進程的地址空間中——無論是擴展已經存在的內存區域還是創建一個新的區域。
do_mmap()函數聲明在文件中,原型如下:
unsigned long do_mmap(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flag, unsigned long offset)
在用戶空間可以通過mmap()函數調用獲取內核函數do_mmap()的功能。mmap()系統調用原型如下:
void *mmap2(void *start, size_t length,
int prot, int flags,
int fd, off_t pgoff)
do_munmap()函數從特定的進程地址空間中刪除指定地址區間,該函數在文件中聲明:
int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
系統調用munmap()給用戶空間程序提供了一種從自身地址空間中刪除指定地址區間的方法,它和系統調用mmap()的作用相反:
int munmap(void *start, size_t length)
mmap設備操作
對於驅動程序來說,內存映射可以提供給用戶程序直接訪問設備內存的能力。映射一個設備,意味著使用戶空間的一段地址關聯到設備內存上。無論何時,只要程序在分配的地址范圍內進行讀取或者寫入,實際上就是對設備的訪問。
並不是所有的設備都能進行mmap抽象。例如,串口設備和其他面向流的設備就無法實現這種抽象。mmap的另一個限制是映射都是以 PAGE_SIZE為單位的。內核只能在頁表一級處理虛擬地址;因此,被映射的區域必須是PAGE_SIZE的整數倍,而且必須位於起始於 PAGE_SIZE整數倍地址的物理內存內。如果區域的大小不是頁大小的整數倍,內核就通過生成一個稍微大一些的區域來容納它。
mmap方法是file_operations結構中的一員,並且在執行mmap系統調用時就會調用該方法。在調用實際方法之前,內核會完成很多工作,而且該方法的原型與系統調用的原型由很大區別。關於Linux命令的介紹,看看《linux就該這么學》,具體關於這一章地址3w(dot)linuxprobe/chapter-02(dot)html
文件操作聲明如下:
int (*mmap) (struct file * filp, struct vm_area_struct *vma);
其中vma參數包含了用於訪問設備的虛擬地址區間的信息。大部分工作已經由內核完成了,要實現mmap,驅動程序只要為這一地址范圍構造合適的頁表即可,如果需要的話,就用一個新的操作集替換vma->vm_ops。
有兩種建立頁表的方法:使用remap_page_range函數可一次建立所有的頁表,或者通過nopage VMA方法每次建立一個頁表。
構造用於映射一段物理地址的新頁表的工作是由remap_page_range完
④ linux共享內存和mmap的區別
共享內存允許兩個或多個進程共享一給定的存儲區,因為數據不需要來回復制,所內以是最快的一容種進程間通信機制。共享內存可以通過mmap()映射普通文件
(特殊情況下還可以採用匿名映射)機制實現,也可以通過系統V共享內存機制實現。應用介面和原理很簡單,內部機制復雜。為了實現更安全通信,往往還與信號燈等同步機制共同使用。
mmap的機制如:就是在磁碟上建立一個文件,每個進程存儲器裡面,單獨開辟一個空間來進行映射。如果多進程的話,那麼不會對實際的物理存儲器(主存)消耗太大。
shm的機制:每個進程的共享內存都直接映射到實際物理存儲器裡面。
1、mmap保存到實際硬碟,實際存儲並沒有反映到主存上。優點:儲存量可以很大(多於主存);缺點:進程間讀取和寫入速度要比主存的要慢。
2、shm保存到物理存儲器(主存),實際的儲存量直接反映到主存上。優點,進程間訪問速度(讀寫)比磁碟要快;缺點,儲存量不能非常大(多於主存)
使用上看:如果分配的存儲量不大,那麼使用shm;如果存儲量大,那麼使用mmap。
⑤ 請教高手:如何在linux下用mmap映射超大文件
manpage裡面的東西:來
void *mmap(void *start, size_t length, int prot, int flags,
int fd, off_t offset);
The mmap() function asks to map length bytes starting at offset offset from the file (or other object) specified by the file descriptor fd into memory,
就是源說,從offset位置開始,把文件fd的length位元組映射到地址start上。
如果是64位的應用,4G是沒有問題的,32位的應用不能。