将最近网上搜索的资料统一整理下,方便后续复查。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…