存储映射--mmap

本文详细介绍了存储映射的概念及其在进程间通信中的应用。通过mmap函数,一个磁盘文件可以与存储空间中的缓冲区相映射,实现高效的数据读写。文章提供了使用mmap和munmap函数的具体示例,展示了如何在两个进程中共享和更新同一份文件数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

存储映射

  • 使一个磁盘文件与存储空间中的一个缓冲区相映射。
  • 当从缓冲区中取数据,就相当于读文件中的相应字节。
  • 将数据存入缓冲区,则相应的字节就自动写入文件。

使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap函数来实现。不通过IO。直接操作内存,效率更高。

mmap函数

函数原型

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);

参数分析

  • addr : 建立映射区的首地址,由Linux内核指定。使用时,直接传递NULL。
  • length : 映射长度。
  • prot: 映射区权限。
    • PROT_READ:只读。
    • PROT_WRITE:只写。
    • PROT_READ | PROT_WRITE:可读可写。
    • 其它参数不常用,参照手册。
  • flags: 标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区).
    • MAP_SHARED:会将映射区所做的操作反映到物理设备(磁盘)上。
    • MAP_PRIVATE: 映射区所做的修改不会反映到物理设备。
    • MAP_ANON:匿名映射,不需要已存在的文件进行映射。fd传-1,只能用于亲缘进程间。
  • fd: 用来建立映射区的文件描述符。
  • offset: 起点的偏移量,必须是4K的整数倍。因为映射至少一页,比如5000字节的文件,映射内存也是2页大小,不会正好是5000.

Note

使用MAP_SHARED的时候,要注意打开文件的权限>=映射区权限。因为如果文件没有写权限,映射区有写权限,那么映射区是无法写入文件的,这和MAP_SHARED的目的相反。如是MAP_PRIVATE就没有此要求。

另外,文件打开权限起码要是可读的,如果不可读,那么怎么读取数据映射到内存呢?

返回值

  • 成功调用返回映射的地址。
  • 失败时返回MAP_FAILED,即void * (-1)。设置errno.

munmap函数

函数原型:

#include <sys/mman.h>
int munmap(void *addr, size_t length);

此函数较为简单,释放映射区,首地址为addr,长度为length.

  • 成功的时候返回0.
  • 失败返回-1且置errno。

应用实例

实现进程间的通信,写进程将一份文件映射到内存,并且每秒写入(覆盖写入)不同的字符串,读进程一直去读。

写进程:

struct Person{         
      char name[30];     
      int num;           
  };
int main(int argc, char const* argv[])
{
    //打开文件,作为映射
    int fd = open("memTest2.txt", O_RDWR);
    //int fd = open("memTest2.txt", O_RDWR);
    int length = sizeof(struct Person);
    ftruncate(fd, length);
    printf("fd=%d\n", fd);
    //映射
    struct Person* mem = (struct Person*)mmap(0, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (mem == MAP_FAILED) {
        perror("mmap err");
        return -1;
    }
    printf("映射区地址:%0x\n", mem);
    int num = 1;
    while (1) {
        mem->num = num++;
        sprintf(mem->name, "I am a Person%03d", mem->num);
        sleep(1);
    }
  
    //释放
    munmap(mem, length);
    close(fd);
    return 0;
    }

读进程:

struct Person{         
      char name[30];     
      int num;           
  };
  
  int main(int argc, char const* argv[]) 
  {
      //打开文件,作为映射
      int fd = open("memTest2.txt", O_RDWR);
      int length = sizeof(struct Person);
      printf("fd=%d\n", fd);    
      //映射             
      struct Person* mem = (struct Person *)mmap(0, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
      if(mem == MAP_FAILED)
      {
          perror("mmap err");       
          return -1;     
      }
      printf("映射区地址:%0x\n", mem);
      while (1) {        
          printf("name=%s,num=%d\n", mem->name,mem->num);
          sleep(1);      
      }                  
  
      //释放
      munmap(mem, length);
      close(fd);         
      return 0;          
  }
### 解决 `syscall-mmap undefined` 错误 当遇到 `syscall-mmap undefined` 的错误时,通常是因为链接器无法找到与 `mmap` 系统调用相关的定义。这可能是由于缺少必要的库文件或者编译选项不正确引起的。 以下是可能的原因以及解决方案: #### 原因分析 1. **未链接正确的库** 如果程序依赖于某些特定的 C 库函数(如 `mmap`),但在编译或链接阶段没有指定这些库,则可能会导致此类错误。例如,在 Linux 下使用 GCC 编译时,默认情况下会链接标准 C 库 (`libc`) 和其他必要库[^1]。 2. **目标平台差异** 不同的操作系统或架构对系统调用的支持有所不同。如果程序是在一种平台上开发并试图运行到另一种平台上,可能导致兼容性问题。 3. **汇编代码中的直接调用** 若程序中有手写的汇编部分直接调用了 `syscall` 指令而未提供完整的参数列表或上下文环境设置不当,也可能引发此问题。 #### 解决策略 ##### 方法一:确认是否遗漏了 `-lc` 参数 在大多数现代 UNIX/Linux 平台上,GCC 默认已经包含了标准C库(`-lc`)。但如果手动指定了较低级别的链接方式(比如仅通过ld),则需显式加入该标志来确保能够访问像 mmap 这样的基本功能。 ```bash gcc your_program.c -o output_binary -lc ``` ##### 方法二:检查 NASM/YASM 组装语法 如果是基于 x86/x86_64 架构下编写的手动嵌入式汇编代码片段,请注意不同版本之间可能存在细微差别;另外还需保证寄存器分配合理且遵循 ABI(Application Binary Interface)规范。 对于 64-bit Linux 上实现 mmap 调用的一个简单例子如下所示: ```nasm section .data ; 数据区声明... section .bss buf resq 1 ; 预留空间用于存储返回地址 section .text global _start ; 入口标签名 _start: mov rax, 9 ; 设置rax=9 表示我们要执行的是第9号系统调用即mmap() xor edi,edi ; 地址设为空(NULL),让内核决定映射位置 mov esi,4096 ; 映射大小为一页(通常是4KB) mov edx,PROT_READ|PROT_WRITE ; 访问权限读写模式 mov ecx,MAP_PRIVATE|MAP_ANONYMOUS ; 文件描述符fd=-1匿名内存区域flag xor r8d,r8d ; fd=-1表示不需要关联任何实际存在的文件对象 xor eax,eax ; offset=0偏移量起点处开始计算长度 syscall ; 发起真正的系统请求操作 test rax,rax ; 测试结果状态码正负情况判断成功与否 js error ; 如果失败跳转至error处理流程分支去打印消息退出进程等动作... success: ; 成功后的逻辑继续往下走即可省略具体细节说明啦~ ... error: lea rdi,[rel msg_fail] call printf mov eax,60 ; exit() system call number. xor edi,edi ; status code to return upon termination. syscall ; invoke operating-system service. msg_fail db 'System Call Failed!',0xA,0xD ``` ##### 方法三:更新工具链版本 有时旧版编译器或连接器内部存在 bug 导致无法正常解析新特性支持状况下的某些指令集扩展形式表达含义。因此建议尝试升级整个开发套件集合直至最新稳定发行版本为止再重新构建项目工程试试看效果如何变化吧! --- ### 提供的相关调试技巧补充材料 上述提到 gdb 工具可以用来逐步跟踪程序崩溃前的状态信息以便定位根本原因所在之处。下面给出一段关于如何利用它查看全局变量表(_GLOBAL_OFFSET_TABLE_)及其附近内存布局结构的小教程作为参考资料学习之用: 假设我们有一个简单的 ELF 可执行文件名为 a.out ,现在想探究其中某个符号的实际物理存放基址是多少可以通过以下命令序列完成查询过程: ```plaintext (gdb) file ./a.out # 加载待测应用程序镜像副本进入调试环境当中加载起来准备就绪等待进一步指示行动方案制定完毕之后就可以正式开始了哦小伙伴们加油干吧!!! Reading symbols from /path/to/a.out...done. (gdb) info functions @got.plt # 列举所有已知导入外部共享动态链接库所导出公开接口成员清单一览表出来看看有没有我们需要的那个家伙呢?? All defined functions matching regular expression "@got\.plt": Non-debugging symbols: 0x0000000000400410 __libc_start_main@plt 0x0000000000400420 puts@plt ...(此处省略其余无关紧要的部分内容)... (gdb)x/10gx &_GLOBAL_OFFSET_TABLE_[0] # 查阅_GOT_首项往后连续十个单元格字节级分布形态特征图谱展示给大家欣赏一下咯嘿嘿嘿😄😄😄 ``` 以上就是针对您提出的有关解决 `syscall-mmap undefined` 问题的一些见解分享希望能帮得到忙😊😊😊
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值