linux函数 mmap详解

发布时间:2023-06-09 21:27:58 作者:yexindonglai@163.com 阅读(987)

mmap函数说明

mmap() 可以把磁盘文件的一部分直接映射到内存,这样文件中的位置直接就有对应的内存 地址,对文件的读写可以直接用指针来做而不需要read/write函数。
函数参数说明:

  1. 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多个进程对同一个文件的映射不是共享的,一个进程对映射的内存做了修改,另一个进程并不会看到这种变化,也不会真的写到文件中去。

一、用法

  1. int main(){
  2. // 文件的权限,0644表示用户拥有者具有读写权限,组用户和其它用户具有只读权限,具体说明请看博客:https://blog.csdn.net/mlz_2/article/details/105250259
  3. const int32_t mode = 0644;
  4. // 以读写方式打开文件,返回文件描述符;
  5. // O_RDWR :文件可读可写
  6. // O_CREAT :若文件不存在,自动创建.注意:不会自动创建目录,目录必须存在,否则创建失败
  7. // O_LARGEFILE :open函数在32位机器下,不能直接支持超过2G的文件。加上 O_LARGEFILE 参数就可以支持,O_LARGEFILE 就是为了大文件读写准备的参数
  8. int fd = open("/root/2.txt",O_RDWR|O_CREAT|O_LARGEFILE,mode);
  9. //stat结构体内存储的是文件信息
  10. struct stat fileStat;
  11. // 打开文件信息,并将信息注入到 fileStat变量,通过文件描述符获取文件对应的属性
  12. fstat(fd, &fileStat);
  13. printf("文件大小:%ld\n",fileStat.st_size);
  14. int len = 128;
  15. // 开启内存映射,成功完成后,mmap()返回一个指向映射的指针区域。否则,将返回值MAP_FAILED,并将errno设置为指示错误。
  16. char *data = (char *)mmap(nullptr,len , PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
  17. if(data == MAP_FAILED){ // 其实 MAP_FAILED 就是空指针
  18. // 若失败,打印错误码
  19. ::fprintf(stderr,"map failed,errno:%s\n",strerror(errno));
  20. data = nullptr;
  21. return errno;
  22. }
  23. // 将0 ~ 128字节的内容都设为x
  24. memset(data,'x',len);
  25. return 0;
  26. }

1、运行

以上代码看似没啥问题,我们假设 2.txt 文件不存在,运行后会虽然自动创建文件,但是在运行的过程中崩溃自动退出了;如下图

可以看到程序崩溃后返回了 255 的错误码了!在C++中,程序崩溃后返回255表示程序异常终止。这通常是由于程序访问了无效的内存地址、发生了除以零等错误导致的。在操作系统中,返回值为255通常表示程序执行失败或出现了未知的错误。

2、文件扩容

大家发现没,上面的程序之所以会崩溃,是因为文件的大小为0啊,你文件都没有容量,你咋装的下内容呢? 那么接下来,我们就需要改造一下,加上扩容的逻辑;如下:

  1. int main(){
  2. // 文件的权限,0644表示用户拥有者具有读写权限,组用户和其它用户具有只读权限,具体说明请看博客:https://blog.csdn.net/mlz_2/article/details/105250259
  3. const int32_t mode = 0644;
  4. // 以读写方式打开文件,返回文件描述符;
  5. // O_RDWR :文件可读可写
  6. // O_CREAT :若文件不存在,自动创建.注意:不会自动创建目录,目录必须存在,否则创建失败
  7. // O_LARGEFILE :open函数在32位机器下,不能直接支持超过2G的文件。加上 O_LARGEFILE 参数就可以支持,O_LARGEFILE 就是为了大文件读写准备的参数
  8. int fd = open("/root/2.txt",O_RDWR|O_CREAT|O_LARGEFILE,mode);
  9. //stat结构体内存储的是文件信息
  10. struct stat fileStat;
  11. // 打开文件信息,并将信息注入到 fileStat变量,通过文件描述符获取文件对应的属性
  12. fstat(fd, &fileStat);
  13. printf("文件大小:%ld\n",fileStat.st_size);
  14. int len = 128;
  15. // 开启内存映射,成功完成后,mmap()返回一个指向映射的指针区域。否则,将返回值MAP_FAILED,并将errno设置为指示错误。
  16. char *data = (char *)mmap(nullptr,len , PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
  17. if(data == MAP_FAILED){ // 其实 MAP_FAILED 就是空指针
  18. // 若失败,打印错误码
  19. ::fprintf(stderr,"map failed,errno:%s\n",strerror(errno));
  20. data = nullptr;
  21. return errno;
  22. }
  23. // 判断文件大小是否大于写入的内容
  24. if(fileStat.st_size < len){
  25. printf("触发文件扩容\n");
  26. // 文件扩容,如果不扩容,写入的内容太大的话,文件就装不下这么大的内容,会自动截取内容;
  27. ftruncate(fd, len);
  28. }
  29. // 将0 ~ 128字节的内容都设为x
  30. memset(data,'x',len);
  31. return 0;
  32. }

关键代码

  1. ftruncate(fd, len);

现在我们再次运行,发下一切正常了,也没有崩溃

文件里面也确实写入了128个x,不信的小伙伴可以数数看;

二、mmap的坑:修改offset参数

接下来我们来修改一下 mmap函数中 offset 的值,改为4,表示从文件的第4个字节开始映射,映射128个字节,因为字节数是从0开始的,也就是映射了文件的第 5~133 字节的内容;

  1. int main(){
  2. const int32_t mode = 0644;
  3. int fd = open("/root/2.txt",O_RDWR|O_CREAT|O_LARGEFILE,mode);
  4. struct stat fileStat;
  5. fstat(fd, &fileStat);
  6. printf("文件大小:%ld\n",fileStat.st_size);
  7. int len = 128;
  8. char *data = (char *)mmap(nullptr,len , PROT_READ|PROT_WRITE, MAP_SHARED, fd, 4);
  9. if(data == MAP_FAILED){
  10. ::fprintf(stderr,"map failed,errno:%s\n",strerror(errno));
  11. data = nullptr;
  12. return errno;
  13. }
  14. // 判断文件大小是否大于写入的内容
  15. if(fileStat.st_size < len){
  16. printf("触发文件扩容\n");
  17. // 文件扩容,如果不扩容,写入的内容太大的话,文件就装不下这么大的内容,会自动截取内容;
  18. ftruncate(fd, len);
  19. }
  20. memset(data,'x',len);
  21. return 0;
  22. }

但是运行后他又报错了,这次不是崩溃,而是程序捕捉到了错误信息,手动进行退出运行的操作;这个错误信息也很明显,
Invalid argument 表示无效的参数;

还记得我们文章最开始的函数说明里面吗?offset参数必须是页大小4kb的整数倍,这个4kb指的是4096 字节,而不是4 字节,但是现在又有一个问题,我们文件没这么大呀,只有128个字节,怎么办呢?其实很简单,既然不够,那就扩大一点;超过4096就行啦;那么就先扩大文件到4224字节,然后在从4096开始映射128个字节; 为什么是4224呢?因为 4096+128 =4224,代码如下:

  1. int main(){
  2. // 文件的权限,0644表示用户拥有者具有读写权限,组用户和其它用户具有只读权限,具体说明请看博客:https://blog.csdn.net/mlz_2/article/details/105250259
  3. const int32_t mode = 0644;
  4. // 以读写方式打开文件,返回文件描述符;
  5. // O_RDWR :文件可读可写
  6. // O_CREAT :若文件不存在,自动创建.注意:不会自动创建目录,目录必须存在,否则创建失败
  7. // O_LARGEFILE :open函数在32位机器下,不能直接支持超过2G的文件。加上 O_LARGEFILE 参数就可以支持,O_LARGEFILE 就是为了大文件读写准备的参数
  8. int fd = open("/root/2.txt",O_RDWR|O_CREAT|O_LARGEFILE,mode);
  9. //stat结构体内存储的是文件信息
  10. struct stat fileStat;
  11. // 打开文件信息,并将信息注入到 fileStat变量,通过文件描述符获取文件对应的属性
  12. fstat(fd, &fileStat);
  13. printf("文件大小:%ld\n",fileStat.st_size);
  14. int len = 4224;
  15. // 开启内存映射,成功完成后,mmap()返回一个指向映射的指针区域。否则,将返回值MAP_FAILED,并将errno设置为指示错误。
  16. char *data = (char *)mmap(nullptr,len , PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
  17. if(data == MAP_FAILED){ // 其实 MAP_FAILED 就是空指针
  18. // 若失败,打印错误码
  19. ::fprintf(stderr,"map failed,errno:%s\n",strerror(errno));
  20. data = nullptr;
  21. return errno;
  22. }
  23. // 判断文件大小是否大于写入的内容
  24. if(fileStat.st_size < len){
  25. printf("触发文件扩容\n");
  26. // 文件扩容,如果不扩容,写入的内容太大的话,文件就装不下这么大的内容,会自动截取内容;
  27. ftruncate(fd, len);
  28. }
  29. memset(data,'x',len);
  30. // 解除映射
  31. munmap(data,len);
  32. // 重新映射,从4096开始映射128个字节
  33. int new_len = 128;
  34. data = (char *)mmap(nullptr,new_len , PROT_READ|PROT_WRITE, MAP_SHARED, fd, 4096);
  35. // 将 第4906之后的128个字节都设为c
  36. memset(data,'c',new_len);
  37. // 解除映射
  38. munmap(data,new_len);
  39. close(fd);
  40. return 0;
  41. }

运行后控制台显示以下内容,没有报错,很好

在看看文件,最后的128个字节都是 c,至此,完美!!!

初学者学习mmap其实并不友好,因为学习资料太少了,特别是有一些坑,初学者是一定会遇到的;

关键字linux