簡述:
mmap函數將文件系統內的文件或者是Posix共享內存對象映射到調用進程的地址空間。
用途:
1.對普通文件使用mmap提供內存映射I/O,以避免系統調用(read、write、lseek)帶來的性能開銷。同時減少了數據在內核緩沖區和進程地址空間的拷貝次數。
2.使用特殊文件提供匿名內存映射。
3.使用shm_open以提供無親緣關系進程間的Posix共享內存區。
接口說明:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
mmap返回成功后,fd文件描述符可以關閉且不會對內存映射產生影響。
對內存區的保護由prot參數指定:
PROT_EXEC:數據可執行
PROT_READ:數據可讀
PROT_WRITE:數據可寫
PROT_NONE:數據不可訪問
對映射內存區的操作標志由flags指定:
MAP_SHARED:對內存區數據操作的變動是共享的。即所有映射到該內存地址的進程都能收到該數據的變動。
MAP_PRIVATE:對內存區數據的操作進行寫時復制。(注:僅僅在內存中有多一份拷貝,文件並不受到影響)
mmap函數通過文件描述符定位到文件位置再到用戶地址空間:
int munmap(void *addr, size_t len);
從某個進程的地址空間刪除一個內存映射關系。len是映射區的大小。
如果被映射區是使用MAP_PRIVATE標志映射的,那么調用進程對它的所有變動都不會寫回文件,並且被丟棄。
細節:
1.考慮不同的進程調用mmap內存映射同一個文件(前提設置了MAP_SHARED),那么某個進程對這個內存映射的修改是否會影響其他進程的內存映射。
//orgin.c用於打印內存映射區內的值 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> int main() { int fd = open("./testfile", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR); int num = 0; write(fd, &num, sizeof(int)); int *ptr = mmap(NULL, sizeof(int), PROT_WRITE, MAP_PRIVATE, fd, 0); sleep(10); printf("%d\n", *ptr); }
//mod.c用於修改內存映射區內的數據 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> int main() { int fd = open("./testfile", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR); int num = 0; write(fd, &num, sizeof(int)); int *ptr = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); printf("%d\n", ++(*ptr)); }
結論:
當不同的進程映射同一個文件的時候,內存映射區其實變為內核為各個進程維護的共享內存區。各個進程對內存映射區的修改都會寫回文件,並影響其他進程在該共享內存上的值。
2.內存分配策略以頁為單位(假設一頁為4096個字節)。考慮內存映射空間和文件大小都為5000個字節的時候,文件映射的讀寫會如何。
#include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> int main() { int fileSize = 5000; int mapSize = 5000; long pageSize; int fd = open("./testfile", O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IRUSR); lseek(fd, fileSize-1, SEEK_SET); write(fd, "", 1); char *ptr = mmap(NULL, mapSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); pageSize = sysconf(_SC_PAGESIZE); int i; for (i = 0; i < 5000; i+=pageSize) { //打印每個頁的首字節內容,並置為1 printf("ptr[%d] = %d\n", i, ptr[i]); ptr[i] = 1; //打印每個頁的尾字節內容,並置為1 printf("ptr[%d] = %d\n", i + pageSize - 1, ptr[i+pageSize-1]); ptr[i+pageSize-1] = 1; } return 0; }
輸出結果:
ptr[0] = 0 ptr[4095] = 0 ptr[4096] = 0 ptr[8191] = 0
使用命令:od -b(以八進制形式查看內容) -A b(以十進制輸出地址) testfile 查看地址對應的內容。
結果:
內存映射其實分配了2個頁的大小(第一個頁0~4095字節,第二個頁4096~8191字節),對於在這兩個頁范圍內的讀和寫操作都不會導致“Segmentation Fault”,但對在5000~8191字節范圍內的寫操作不會寫回文件中(因為文件大小為5000字節)。對於超過8191字節(即第三個頁)的讀寫將導致段錯誤。
結論:
在內存分配的頁范圍內的讀寫不會導致段錯誤,在文件大小范圍內的寫操作才會寫回文件。
3.進程采用mmap操作文件內容一定比read\write快嗎?
討論這個問題前先看進程采用系統條用read/write方式讀寫文件過程。
數據先從文件系統復制到內核緩沖區(最小單位為一個頁大小)---->復制數據到用戶的進程空間;此過程中如果進程的下一次讀操作的數據依然在內核緩沖區的話內核不會再從文件系統中讀取,而是將數據從內核緩沖區直接復制給進程。
后續對文件的操作還需要其他的系統調用如lseek\write。進程將多次在內核態和用戶態之間切換。而mmap則只需一次系統調用,其后操作都在用戶態上。
由此可知:
對於小文件的讀寫操作,內核完全有可能直接從內核緩沖區讀寫數據並不見得會比mmap慢,但是對於大文件且頻繁讀寫的操作,mmap會比read\write要快。
完。
you knon i am not leaving you.right?