mmap函数说明
mmap() 可以把磁盘文件的一部分直接映射到内存,这样文件中的位置直接就有对应的内存 地址,对文件的读写可以直接用指针来做而不需要read/write函数。
函数参数说明:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数说明:
- addr: mmap函数中如果addr参数为NULL,内核会自己在进程地址空间中选择合适的地址建立映射。如果addr不是NULL,则给内核一个提示,应该从什么地址开始映射,内核会选择addr之上的某个合适的地址开始映射。建立映射后,真正的映射首地址通过返回值可以得到。
- length: len参数是需 要映射的那一部分文件的长度。
- offset:offset参数表示从文件的什么位置开始映射,必须是页大小4kb的整数倍(在32位体系统结构上通常是4K,也就是4096 、8192、12288。。。。)
- fd:filedes是代表该文件的描述符。
- prot:prot参数有四种取值:
- PROT_EXEC表示映射的这一段可执行,例如映射共享库
- PROT_READ表示映射的这一段可读
- PROT_WRITE表示映射的这一段可写
- PROT_NONE表示映射的这一段不可访问
- flags:flags参数有很多种取值,常用的两种如下,其它取值可通过命令
man mmap
查看- MAP_SHARED多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修 改,另一个进程也会看到这种变化。
- MAP_PRIVATE多个进程对同一个文件的映射不是共享的,一个进程对映射的内存做了修改,另一个进程并不会看到这种变化,也不会真的写到文件中去。
一、用法
int main(){
// 文件的权限,0644表示用户拥有者具有读写权限,组用户和其它用户具有只读权限,具体说明请看博客:https://blog.csdn.net/mlz_2/article/details/105250259
const int32_t mode = 0644;
// 以读写方式打开文件,返回文件描述符;
// O_RDWR :文件可读可写
// O_CREAT :若文件不存在,自动创建.注意:不会自动创建目录,目录必须存在,否则创建失败
// O_LARGEFILE :open函数在32位机器下,不能直接支持超过2G的文件。加上 O_LARGEFILE 参数就可以支持,O_LARGEFILE 就是为了大文件读写准备的参数
int fd = open("/root/2.txt",O_RDWR|O_CREAT|O_LARGEFILE,mode);
//stat结构体内存储的是文件信息
struct stat fileStat;
// 打开文件信息,并将信息注入到 fileStat变量,通过文件描述符获取文件对应的属性
fstat(fd, &fileStat);
printf("文件大小:%ld\n",fileStat.st_size);
int len = 128;
// 开启内存映射,成功完成后,mmap()返回一个指向映射的指针区域。否则,将返回值MAP_FAILED,并将errno设置为指示错误。
char *data = (char *)mmap(nullptr,len , PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(data == MAP_FAILED){ // 其实 MAP_FAILED 就是空指针
// 若失败,打印错误码
::fprintf(stderr,"map failed,errno:%s\n",strerror(errno));
data = nullptr;
return errno;
}
// 将0 ~ 128字节的内容都设为x
memset(data,'x',len);
return 0;
}
1、运行
以上代码看似没啥问题,我们假设 2.txt 文件不存在,运行后会虽然自动创建文件,但是在运行的过程中崩溃自动退出了;如下图
可以看到程序崩溃后返回了 255 的错误码了!在C++中,程序崩溃后返回255表示程序异常终止。这通常是由于程序访问了无效的内存地址、发生了除以零等错误导致的。在操作系统中,返回值为255通常表示程序执行失败或出现了未知的错误。
2、文件扩容
大家发现没,上面的程序之所以会崩溃,是因为文件的大小为0啊,你文件都没有容量,你咋装的下内容呢? 那么接下来,我们就需要改造一下,加上扩容的逻辑;如下:
int main(){
// 文件的权限,0644表示用户拥有者具有读写权限,组用户和其它用户具有只读权限,具体说明请看博客:https://blog.csdn.net/mlz_2/article/details/105250259
const int32_t mode = 0644;
// 以读写方式打开文件,返回文件描述符;
// O_RDWR :文件可读可写
// O_CREAT :若文件不存在,自动创建.注意:不会自动创建目录,目录必须存在,否则创建失败
// O_LARGEFILE :open函数在32位机器下,不能直接支持超过2G的文件。加上 O_LARGEFILE 参数就可以支持,O_LARGEFILE 就是为了大文件读写准备的参数
int fd = open("/root/2.txt",O_RDWR|O_CREAT|O_LARGEFILE,mode);
//stat结构体内存储的是文件信息
struct stat fileStat;
// 打开文件信息,并将信息注入到 fileStat变量,通过文件描述符获取文件对应的属性
fstat(fd, &fileStat);
printf("文件大小:%ld\n",fileStat.st_size);
int len = 128;
// 开启内存映射,成功完成后,mmap()返回一个指向映射的指针区域。否则,将返回值MAP_FAILED,并将errno设置为指示错误。
char *data = (char *)mmap(nullptr,len , PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(data == MAP_FAILED){ // 其实 MAP_FAILED 就是空指针
// 若失败,打印错误码
::fprintf(stderr,"map failed,errno:%s\n",strerror(errno));
data = nullptr;
return errno;
}
// 判断文件大小是否大于写入的内容
if(fileStat.st_size < len){
printf("触发文件扩容\n");
// 文件扩容,如果不扩容,写入的内容太大的话,文件就装不下这么大的内容,会自动截取内容;
ftruncate(fd, len);
}
// 将0 ~ 128字节的内容都设为x
memset(data,'x',len);
return 0;
}
关键代码
ftruncate(fd, len);
现在我们再次运行,发下一切正常了,也没有崩溃
文件里面也确实写入了128个x,不信的小伙伴可以数数看;
二、mmap的坑:修改offset参数
接下来我们来修改一下 mmap函数中 offset 的值,改为4,表示从文件的第4个字节开始映射,映射128个字节,因为字节数是从0开始的,也就是映射了文件的第 5~133 字节的内容;
int main(){
const int32_t mode = 0644;
int fd = open("/root/2.txt",O_RDWR|O_CREAT|O_LARGEFILE,mode);
struct stat fileStat;
fstat(fd, &fileStat);
printf("文件大小:%ld\n",fileStat.st_size);
int len = 128;
char *data = (char *)mmap(nullptr,len , PROT_READ|PROT_WRITE, MAP_SHARED, fd, 4);
if(data == MAP_FAILED){
::fprintf(stderr,"map failed,errno:%s\n",strerror(errno));
data = nullptr;
return errno;
}
// 判断文件大小是否大于写入的内容
if(fileStat.st_size < len){
printf("触发文件扩容\n");
// 文件扩容,如果不扩容,写入的内容太大的话,文件就装不下这么大的内容,会自动截取内容;
ftruncate(fd, len);
}
memset(data,'x',len);
return 0;
}
但是运行后他又报错了,这次不是崩溃,而是程序捕捉到了错误信息,手动进行退出运行的操作;这个错误信息也很明显,Invalid argument
表示无效的参数;
还记得我们文章最开始的函数说明里面吗?offset参数必须是页大小4kb的整数倍,这个4kb指的是4096 字节,而不是4 字节,但是现在又有一个问题,我们文件没这么大呀,只有128个字节,怎么办呢?其实很简单,既然不够,那就扩大一点;超过4096就行啦;那么就先扩大文件到4224字节,然后在从4096开始映射128个字节; 为什么是4224呢?因为 4096+128 =4224,代码如下:
int main(){
// 文件的权限,0644表示用户拥有者具有读写权限,组用户和其它用户具有只读权限,具体说明请看博客:https://blog.csdn.net/mlz_2/article/details/105250259
const int32_t mode = 0644;
// 以读写方式打开文件,返回文件描述符;
// O_RDWR :文件可读可写
// O_CREAT :若文件不存在,自动创建.注意:不会自动创建目录,目录必须存在,否则创建失败
// O_LARGEFILE :open函数在32位机器下,不能直接支持超过2G的文件。加上 O_LARGEFILE 参数就可以支持,O_LARGEFILE 就是为了大文件读写准备的参数
int fd = open("/root/2.txt",O_RDWR|O_CREAT|O_LARGEFILE,mode);
//stat结构体内存储的是文件信息
struct stat fileStat;
// 打开文件信息,并将信息注入到 fileStat变量,通过文件描述符获取文件对应的属性
fstat(fd, &fileStat);
printf("文件大小:%ld\n",fileStat.st_size);
int len = 4224;
// 开启内存映射,成功完成后,mmap()返回一个指向映射的指针区域。否则,将返回值MAP_FAILED,并将errno设置为指示错误。
char *data = (char *)mmap(nullptr,len , PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(data == MAP_FAILED){ // 其实 MAP_FAILED 就是空指针
// 若失败,打印错误码
::fprintf(stderr,"map failed,errno:%s\n",strerror(errno));
data = nullptr;
return errno;
}
// 判断文件大小是否大于写入的内容
if(fileStat.st_size < len){
printf("触发文件扩容\n");
// 文件扩容,如果不扩容,写入的内容太大的话,文件就装不下这么大的内容,会自动截取内容;
ftruncate(fd, len);
}
memset(data,'x',len);
// 解除映射
munmap(data,len);
// 重新映射,从4096开始映射128个字节
int new_len = 128;
data = (char *)mmap(nullptr,new_len , PROT_READ|PROT_WRITE, MAP_SHARED, fd, 4096);
// 将 第4906之后的128个字节都设为c
memset(data,'c',new_len);
// 解除映射
munmap(data,new_len);
close(fd);
return 0;
}
运行后控制台显示以下内容,没有报错,很好
在看看文件,最后的128个字节都是 c,至此,完美!!!
完
初学者学习mmap其实并不友好,因为学习资料太少了,特别是有一些坑,初学者是一定会遇到的;