將最近網上搜索的資料統一整理下,方便後續複查。html
mmap是一種內存映射文件的方法,即將一個文件或者其它對象映射到進程的地址空間,實現文件磁盤地址和進程虛擬地址空間中一段虛擬地址的一一對映關係,函數原型以下 void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);linux
實現這樣的映射關係後,進程就能夠採用指針的方式讀寫操做這一段內存,而系統會自動回寫髒頁面到對應的文件磁盤上,即完成了對文件的操做而沒必要再調用read,write等系統調用函數。以下圖所示 編程
mmap適合於對同一塊區域頻繁讀寫的狀況,好比一個64M的文件存儲了一些索引信息,咱們須要頻繁修改並持久化到磁盤,這樣能夠將文件經過mmap映射到用戶虛擬內存,而後經過指針的方式修改內存區域,由操做系統自動將修改的部分刷回磁盤,也能夠本身調用msync手動刷磁盤。緩存
內核角度分析mmap原理,這篇博客圖文並茂,直接參考就行了。 linux內存映射mmap原理分析 - 魚思故淵的專欄 - CSDN博客 blog.csdn.net/yusiguyuan/…bash
映射只不過是映射到虛擬內存,不用擔憂映射的文件太大。數據結構
linux 進程的虛擬內存 - fengxin的博客 - CSDN博客 blog.csdn.net/fengxinlinu…async
映射文件或設備到內存中,取消映射就是munmap函數。ide
語法以下:函數
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length);性能
該函數主要用途有三個:
咱們來看下函數的入參選擇:
➢ 參數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 對映射區域的寫入操做會產生一個映射文件的複製,即私人的「寫入時複製」(copy on write)對此區域做的任何修改都不會寫回原來的文件內容。
MAP_ANONYMOUS創建匿名映射。此時會忽略參數fd,不涉及文件,並且映射區域沒法和其餘進程共享。
MAP_DENYWRITE只容許對映射區域的寫入操做,其餘對文件直接寫入的操做將會被拒絕。
MAP_LOCKED 將映射區域鎖定住,這表示該區域不會被置換(swap)。
➢ 參數fd:
要映射到內存中的文件描述符。若是使用匿名內存映射時,即flags中設置了MAP_ANONYMOUS,fd設爲-1。
➢ 參數offset:
文件映射的偏移量,一般設置爲0,表明從文件最前方開始對應,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:試着訪問不屬於進程的內存區
不能簡單的說哪一個效率高,要看具體實現與具體應用。 write read mmap實際流程以下:
mmap的優點在於經過把文件的某一塊內容映射到用戶空間上,用戶能夠直接向內核緩衝池讀寫這一塊內容,這樣一來就少了內核與用戶空間的來回拷貝因此一般更快。但 mmap方式只適用於更新、讀寫一塊固定大小的文件區域而不能作像諸如不斷的寫內容進入文件導到文件增加這類的事。
兩者的主要區別在於,與mmap和memcpy相比,read和write執行了更多的系統調用,並作了更多的複製。read和write將數據從內核緩衝區中複製到應用緩衝區,而後再把數據從應用緩衝區複製到內核緩衝區。而mmap和memcpy則直接把數據從映射到地址空間的一個內核緩衝區複製到另外一個內核緩衝區。當引用尚不存在的內存頁時,這樣的複製過程就會做爲處理頁錯誤的結果而出現(每次錯頁讀發生一次錯誤,每次錯頁寫發生一次錯誤)。
因此他們二者的效率的比較就是系統調用和額外的複製操做的開銷和頁錯誤的開銷之間的比較,哪個開銷少就是哪個表現更好。用mmap能夠避免與讀寫打交道,這樣能夠簡化程序邏輯,有利於編程實現。
系統調用mmap()能夠將某文件映射至內存(進程空間),如此能夠把對文件的操做轉爲對內存的操做,以此避免更多的lseek()與read()、write()操做,這點對於大文件或者頻繁訪問的文件而言尤爲受益。但有一點必須清楚:mmap的addr與offset必須對齊一個內存頁面大小的邊界,即內存映射每每是頁面大小的整數倍,不然maaped_file_size%page_size內存空間將被閒置浪費。映射時,指定offset最好是內存頁面大小的整數倍。
內存文件映射的使用:
(1)大數據量文件的讀取,有效的提升磁盤和內存間數據通訊的性能;
(2)進程間快速的共享內存,實現進程間高效的通訊。
內存映射文件性能高於普通IO的緣由:
內存文件映射和普通的文件IO都是要經過文件系統和硬盤驅動拷貝數據到內存中,內存文件映射數據越大越快主要是:
(1)實際拷貝數據前,須要創建映射信息,內存文件映射已經提早準備好了映射關係,內核調度好了進程內的內存塊,交付給內核進行了預先處理,內存文件映射會消耗掉一些時間。
(2)實際拷貝時候,內存文件映射將磁盤數據直接拷貝到用戶進程內存空間只進行了一次拷貝,而普通的IO是先將文件拷貝到內核緩存空間,而後才拷貝到用戶進程內存空間,進行了兩次拷貝。
下面是一個使用普通的fread函數和內存映射文件函數,讀取不一樣大小的磁盤文件的性能分析表:
(1)demo1 演示一下,將文件/tmp/file_mmap中的字符轉成大寫,分別使用mmap與read/write二種方法實現。 先建立/tmp/file_mmap文件,該文件寫入www.baidu.com,使用strace統計系統調用。
/*
* @file: t_mmap.c
*/
#include <stdio.h>
#include <ctype.h>
#include <sys/mman.h> /*mmap munmap*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
char *buf;
off_t len;
struct stat sb;
char *fname = "/tmp/file_mmap";
fd = open(fname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1)
{
perror("open");
return 1;
}
if (fstat(fd, &sb) == -1)
{
perror("fstat");
return 1;
}
buf = mmap(0, sb.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (buf == MAP_FAILED)
{
perror("mmap");
return 1;
}
if (close(fd) == -1)
{
perror("close");
return 1;
}
for (len = 0; len < sb.st_size; ++len)
{
buf[len] = toupper(buf[len]);
/*putchar(buf[len]);*/
}
if (munmap(buf, sb.st_size) == -1)
{
perror("munmap");
return 1;
}
return 0;
}
複製代碼
本身測試運行結果:
root@chenwr-pc:/home/workspace/test# gcc tmp.c -o run
root@chenwr-pc:/home/workspace/test# strace ./run
execve("./run", ["./run"], [/* 22 vars */]) = 0
brk(0) = 0x1ffa000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=106932, ...}) = 0
mmap(NULL, 106932, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fcab05de000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P \2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1857312, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcab05dd000
mmap(NULL, 3965632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fcab000e000
mprotect(0x7fcab01cc000, 2097152, PROT_NONE) = 0
mmap(0x7fcab03cc000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1be000) = 0x7fcab03cc000
mmap(0x7fcab03d2000, 17088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fcab03d2000
close(3) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcab05db000
arch_prctl(ARCH_SET_FS, 0x7fcab05db740) = 0
mprotect(0x7fcab03cc000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ) = 0
mprotect(0x7fcab05f9000, 4096, PROT_READ) = 0
munmap(0x7fcab05de000, 106932) = 0
open("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=14, ...}) = 0
mmap(NULL, 14, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0x7fcab05f8000
close(3) = 0
munmap(0x7fcab05f8000, 14) = 0
exit_group(0) = ?
+++ exited with 0 +++
複製代碼
該文件已經變成大寫。
root@chenwr-pc:/tmp# cat file_mmap
WWW.BAIDU.COM
複製代碼
網上該demo的說明:
open("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3 //open,返回fd=3
fstat64(3, {st_mode=S_IFREG|0644, st_size=18, ...}) = 0 //fstat, 即文件大小18
mmap2(NULL, 18, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0xb7867000 //mmap文件fd=3
close(3) = 0 //close文件fd=3
munmap(0xb7867000, 18)= 0 //munmap,移除0xb7867000這裏的內存映射
這裏mmap的addr是0(NULL),offset是18,並非一個內存頁的整數倍,即有4078bytes(4kb-18)內存空間被閒置浪費了。
複製代碼
(2)demo2 read的方式
#include <stdio.h>
#include <ctype.h>
#include <sys/mman.h> /*mmap munmap*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd, len;
char *buf;
char *fname = "/tmp/file_mmap";
ssize_t ret;
struct stat sb;
fd = open(fname, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
if (fd == -1)
{
perror("open");
return 1;
}
if (fstat(fd, &sb) == -1)
{
perror("stat");
return 1;
}
buf = malloc(sb.st_size);
if (buf == NULL)
{
perror("malloc");
return 1;
}
ret = read(fd, buf, sb.st_size);
for (len = 0; len < sb.st_size; ++len)
{
buf[len] = toupper(buf[len]);
/*putchar(buf[len]);*/
}
lseek(fd, 0, SEEK_SET);
ret = write(fd, buf, sb.st_size);
if (ret == -1)
{
perror("error");
return 1;
}
if (close(fd) == -1)
{
perror("close");
return 1;
}
free(buf);
return 0;
}
複製代碼
本身測試運行的結果:
root@chenwr-pc:/home/workspace/test# strace ./run
execve("./run", ["./run"], [/* 22 vars */]) = 0
brk(0) = 0x13ac000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=106932, ...}) = 0
mmap(NULL, 106932, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fb98f1d7000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P \2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1857312, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb98f1d6000
mmap(NULL, 3965632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb98ec07000
mprotect(0x7fb98edc5000, 2097152, PROT_NONE) = 0
mmap(0x7fb98efc5000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1be000) = 0x7fb98efc5000
mmap(0x7fb98efcb000, 17088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb98efcb000
close(3) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb98f1d4000
arch_prctl(ARCH_SET_FS, 0x7fb98f1d4740) = 0
mprotect(0x7fb98efc5000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ) = 0
mprotect(0x7fb98f1f2000, 4096, PROT_READ) = 0
munmap(0x7fb98f1d7000, 106932) = 0
open("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=14, ...}) = 0
brk(0) = 0x13ac000
brk(0x13cd000) = 0x13cd000
read(3, "www.baidu.com\n", 14) = 14
lseek(3, 0, SEEK_SET) = 0
write(3, "WWW.BAIDU.COM\n", 14) = 14
close(3) = 0
exit_group(0) = ?
+++ exited with 0 +++
複製代碼
網上該demo的說明:
open("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3 //open, fd=3
fstat64(3, {st_mode=S_IFREG|0644, st_size=18, ...}) = 0 //fstat, 其中文件大小18
brk(0) = 0x9845000 //brk, 返回當前中斷點
brk(0x9866000) = 0x9866000 //malloc分配內存,堆當前最後地址
read(3, "www.perfgeeks.com\n", 18)= 18 //read
lseek(3, 0, SEEK_SET) = 0 //lseek
write(3, "WWW.PERFGEEKS.COM\n", 18) = 18 //write
close(3) = 0
複製代碼
這裏經過read()讀取文件內容,toupper()後,調用write()寫回文件。由於文件過小,體現不出read()/write()的缺點:頻繁訪問大文件,須要多個lseek()來肯定位置。每次編輯read()/write(),在物理內存中的雙份數據。 固然,不能夠忽略建立與維護mmap()數據結構的成本。須要注意:並無具體測試mmap vs read/write,即不能一語斷言誰孰誰劣,具體應用場景具體評測分析。 你只是要記住:mmap內存映射文件以後,操做內存便是操做文件,能夠省去很多系統內核調用(lseek, read, write)。
#include <stdio.h>
#include <ctype.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#define INT64U unsigned long long
#define MSG_ERR 1
#define MSG_WARN 2
#define MSG_INFO 3
#define MSG_DBG 4
#define MSG_NOR 5
#define MSG_HEAD ("libfat->")
#define PRTMSG(level, fmt, args...)\
do {\
if (level <= MSG_NOR) {\
if (level <= MSG_NOR) {\
printf("%s, %s, line %d: " fmt,__FILE__,__FUNCTION__,__LINE__, ##args);\
} else {\
printf("%s:" fmt, MSG_HEAD, ##args);\
}\
}\
} while(0)
typedef unsigned char BOOLEAN;
typedef unsigned char INT8U;
typedef unsigned int INT16U;
typedef unsigned long INT32U;
typedef signed char INT8S;
typedef signed int INT16S;
typedef signed long INT32S;
char *filename = "./lt00001";
//char *filename = "/mnt/sdisk/video/lt00004";
char *data = "1111111111\ 2222222222\ 3333333333\ 4444444444";
INT32S data_len = 40;//單次寫入的數據長度
struct timeval t_start, t_end;
struct stat file_info;
long cost_time = 0;
int write_num = 1000;
INT32S mmap_write(INT32S fd, INT64U offset, void *data, INT32S data_len)
{
char *buf = NULL;
if (fstat(fd, &file_info) == -1) {
perror("fstat");
PRTMSG(MSG_ERR, "[cwr] Get file info failed\n");
return -1;
}
buf = mmap(0, file_info.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (buf == MAP_FAILED) {
perror("mmap");
PRTMSG(MSG_ERR, "[cwr] mmap failed\n");
return -1;
}
//offset = (INT64U)((order)*sizeof(FAT_FILE_LIST_T));
memcpy(buf+offset, data, data_len);
if (munmap(buf, file_info.st_size) == -1) {
perror("munmap");
PRTMSG(MSG_ERR, "[cwr] munmap failed\n");
return -1;
}
return data_len;
}
int write_test()
{
int fd, ret, i, data_size;
INT64U ret64, offset;
int ret_len = 0;
time_t starttime, endtime;
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("[cwr] open file faild\n");
}
gettimeofday(&t_start, NULL);
for (i=0; i<write_num; i++) {
offset = i*data_len;
ret64 = lseek64(fd, offset, SEEK_SET);
if (ret64 == -1LL) {
printf("lseek data fail\n");
return -1;
}
ret_len = write(fd, data, data_len);
if (ret_len != data_len) {
printf("[cwr] count = %d; write error\n", i);
close(fd);
return -1;
}
}
gettimeofday(&t_end, NULL);
printf("[cwr] test end, count = %d\n", i);
close(fd);
return 0;
}
int mmap_write_test()
{
int fd, ret, i, data_size;
INT64U ret64, offset;
int ret_len = 0;
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("[cwr] open file faild\n");
}
gettimeofday(&t_start, NULL);
for (i=0; i<write_num; i++) {
offset = i*data_len;
ret_len = mmap_write(fd, offset, data, data_len);
if (ret_len != data_len) {
printf("[cwr] count = %d; mmap write error\n", i);
close(fd);
return -1;
}
}
gettimeofday(&t_end, NULL);
printf("[cwr] mmap write test end, count = %d\n", i);
close(fd);
return 0;
}
void main()
{
int ret;
memset(&file_info, 0, sizeof(file_info));
#if 1
ret = write_test();
if (ret != 0) {
printf("[cwr] write_test failed\n");
}
#endif
#if 0
ret = mmap_write_test();
if (ret != 0) {
printf("[cwr] mmap_write_test failed\n");
}
#endif
cost_time = t_end.tv_usec - t_start.tv_usec;
printf("Start time: %ld us\n", t_start.tv_usec);
printf("End time: %ld us\n", t_end.tv_usec);
printf("Cost time: %ld us\n", cost_time);
while(1) {
sleep(1);
}
}
複製代碼
運行結果:
write的方式獲取的時間
buf = mmap(0, 40, PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset*4*1024)
複製代碼
爲什麼測試mmap效率並無更高效。
mmap函數使用與實例詳解 - u013525455的博客 - CSDN博客 blog.csdn.net/u013525455/…
linux mmap 內存映射 mmap() vs read()/write()/lseek()的實例演示 - Linux操做系統:Ubuntu_Centos_Debian - 紅黑聯盟 www.2cto.com/kf/201806/7…
Linux文件讀寫機制及優化方式 - Linux就該這麼學 - 博客園 www.cnblogs.com/linuxprobe/…
在linux中使用內存映射(mmap)操做文件 - 隨意的風的專欄 - CSDN博客 blog.csdn.net/windgs_yf/a…
linux內存管理——mmap函數詳解 - badman250的專欄 - CSDN博客 blog.csdn.net/notbaron/ar…
函數sync、fsync與fdatasync總結整理 - pugu12的專欄 - CSDN博客 blog.csdn.net/pugu12/arti…
file - Why (ftruncate+mmap+memcpy) is faster than (write)? - Stack Overflow stackoverflow.com/questions/3…
mmap與直接IO(read、write)的效率比較 - 緋淺yousa的筆記 - CSDN博客 blog.csdn.net/qq_15437667…
測試linux下 fprintf fwrite write mmap 等寫文件的速度 - penzchan的專欄 - CSDN博客 blog.csdn.net/penzchan/ar…
mmap和write性能對比-bjpiao-ChinaUnix博客 blog.chinaunix.net/uid-2657535…
linux內存映射mmap原理分析 - 魚思故淵的專欄 - CSDN博客 blog.csdn.net/yusiguyuan/…
linux 進程的虛擬內存 - fengxin的博客 - CSDN博客 blog.csdn.net/fengxinlinu…
[原創] 深刻剖析mmap-從三個關鍵問題提及 - 簡書 www.jianshu.com/p/eece39bee…