【并发程序设计】12.内存映射

12.内存映射

使一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不必再调用read,write,更加高效。

用到的函数

mmap函数

  1. 原型

    #include <sys/mman.h>
    void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);
    
  2. 功能:创建共享内存映射

  3. 参数

    • addr:指定要映射的内存地址,一般设置为 NULL 让操作系统自动选择合适的内存地址。

    • length:必须>0。映射地址空间的字节数,它从被映射文件开头 offset 个字节开始算起。

    • prot:指定共享内存的访问权限。可取如下几个值的可选:

      • PROT_READ(可读)

      • PROT_WRITE(可写)

      • PROT_EXEC(可执行)

      • PROT_NONE(不可访问)

    • flags:由以下几个常值指定:

      1. MAP_SHARED(共享映射)

      2. MAP_PRIVATE(私有映射)

      3. MAP_FIXED(表示必须使用 start 参数作为开始地址,如果失败不进行修正)

      4. MAP_ANONYMOUS(匿名映射,用于血缘关系进程间通信)

        其中,MAP_SHARED , MAP_PRIVATE必选其一,而 MAP_FIXED 则不推荐使用。

    • fd:表示要映射的文件句柄。如果匿名映射写-1。

    • offset:表示映射文件的偏移量,一般设置为 0 表示从文件头部开始映射。

  4. 返回值

    • 成功返回创建的映射区首地址
    • 失败返回MAP_FAILED,设置errno值

lseek函数

  1. 原型

    #include <sys/types.h>
    #include <unistd.h>
    off_t lseek(int fd, off_t offset, int whence);
    
  2. 功能:用于在文件中定位文件指针的位置

  3. 参数

    • fd:文件描述符,通常是通过openfopen函数获得的。

    • offset:相对于whence的偏移量,以字节为单位。

    • whence:表示参考点的位置,可以是以下值之一:

      :表示参考点的位置,可以是以下值之一:

      • SEEK_SET:文件开头
      • SEEK_CUR:文件当前位置
      • SEEK_END:文件结尾
  4. 返回值

    • 成功时,返回新的文件指针位置。
    • 失败时,返回-1,并设置errno

示例1

利用内存映射实现两个无亲缘关系的线程间的通信

write.c

#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{void *addr;int fd;fd = open("test",O_RDWR);if(fd<0){perror("open");return 0;}//获取文件大小int fileSize = lseek(fd, 0, SEEK_END);lseek(fd, 0, SEEK_SET); // 将文件指针重置到文件开头//映射文件到内存addr = mmap(NULL,2000,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);if(addr == MAP_FAILED){perror("mmap");close(fd);return 0;}//写入文件int i = 0;printf("%d\n",fileSize);while(i < 2000){printf("%d\n",i);memcpy(addr+i,"5",1);sleep(1);i++;}return 0;
}

read.c

#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{void *addr;int fd;fd = open("test",O_RDWR);if(fd<0){perror("open");return 0;}//获取文件大小int fileSize = lseek(fd, 0, SEEK_END);lseek(fd, 0, SEEK_SET); // 将文件指针重置到文件开头//映射文件到内存addr = mmap(NULL,2000,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);if(addr == MAP_FAILED){perror("mmap");close(fd);return 0;}//读取int i = 0;printf("%d\n",fileSize);while(i < 2000){printf("%s\n",(char *)addr);sleep(1);}return 0;
}

示例2匿名映射

利用内存映射匿名映射实现两个亲缘线程间的通信

#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>int main(){void *addr;//匿名映射addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);if(addr == MAP_FAILED){perror("mmap");return 0;}//创建子进程pid_t pid;pid = fork();if(pid<0){perror("fork");return 0;}//父进程else if(pid>0){memcpy(addr,"1234567890",10);wait(NULL);//回收子进程}//子进程else {sleep(1);printf("read father val=%s\n",(char *)addr);}munmap(addr,2048);//释放内存
}

注意事项

  1. 创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区。

  2. 当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护),如果不满足报非法参数(Invalid argument)错误。

    当MAP_PRIVATE时候,mmap中的权限是对内存的限制,只需要文件有读权限即可,操作只在内存有效,不会写到物理磁盘,且不能在进程间共享。

  3. 映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭。

  4. 用于映射的文件大小必须>0,当映射文件大小为0时,指定非0大小创建映射区,访问映射地址会报总线错误,指定0大小创建映射区,报非法参数错误(Invalid argument)

  5. 文件偏移量必须为0或者4K的整数倍(不是会报非法参数Invalid argument错误)

  6. 映射大小可以大于文件大小,但只能访问文件page的内存地址,否则报总线错误 ,超出映射的内存大小报段错误

    在这里插入图片描述

  7. 映射内存大小为4K的整数倍,若申请映射大小不为4K的整数倍,则会自动向上补齐。例如申请2k,实际可以操作的内存大小为4K。

System V共享内存

  • 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
  • 共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活
  • 由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用

共享内存使用步骤:

  1. 生成key
  2. 创建/打开共享内存
  3. 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
  4. 读写共享内存
  5. 撤销共享内存映射
  6. 删除共享内存对象

在这里插入图片描述

ftok函数

  1. 原型

    #include <sys/types.h>
    #include <sys/ipc.h>
    key_t **ftok**(const char *pathname, int proj_id);
    
  2. 功能:生成一个唯一的与文件相关的键值(key),这个键值通常用于IPC(进程间通信)中,比如创建共享内存或信号量等。

  3. 参数

    • pathname:必须是存在的且可访问的文件路径。它可以是一个普通文件或者目录,但该路径下的某个文件需要具有相应的权限,以便其他进程可以通过该路径访问到相同的键值。
    • proj_id:是一个8位的整数(即范围在0到255之间)。它与文件的索引节点号结合生成最终的键值。
  4. 返回值

    • 如果函数执行成功,会返回一个key_t类型的键值
    • 如果执行失败,则返回-1
  5. 注意

    • 确保pathname指定的文件存在且可访问。

    • proj_id的值虽然作为int类型传递,但实际上只有8个比特被使用,因此其取值范围是0到255。

    • 生成的键值是由proj_id的后8个比特、文件所在设备的最后两位和文件的索引节点号的最后四位组成的

shmget函数

  1. 原型

    #include <sys/shm.h>
    int shmget(key_t key, size_t size, int shmflg);
    
  2. 功能:获取或创建一个共享内存段,并返回一个共享内存标识符。

  3. 参数

    • key:共享内存的键值,可以是0(IPC_PRIVATE)以创建一个新的共享内存对象,或者是大于0的32位整数。如果shmflg中包含了IPC_CREAT标志,且提供的键值已经存在,则会返回现有的共享内存标识符。
    • size:指定需要共享的内存容量,以字节为单位。
    • shmflg:权限标志,与open函数的mode参数类似。如果希望在键值指定的共享内存不存在时创建它,可以与IPC_CREAT进行或操作。
  4. 返回值

    • 如果成功,shmget函数返回一个共享内存标识符,该标识符可以在后续的共享内存相关操作中使用,如shmatshmdtshmctl等。
    • 如果失败,返回-1。
  5. 注意

    • 不同进程可以通过shmget返回的共享内存标识符访问同一块共享内存。
    • 当写数据到共享内存时,需要注意同步问题,即在进程间访问共享内存时要加锁,以防止数据竞争。
    • 在使用共享内存时,应确保结构体中的缓冲区已经声明了足够的大小,而不是仅使用一个指针并在需要时通过malloc分配内存,因为这样分配的地址其他进程无法访问

ipcs 命令

  1. 格式ipcs [options]
  2. 功能ipcs 命令用于提供系统中进程间通信设施的信息,包括消息队列、共享内存段和信号量。
  3. 参数
    • -a--all:显示所有类型的 IPC 资源信息。
    • -b--broad:显示 IPC 资源的宽泛信息,包括最大允许的尺寸等。
    • -m--shmems:仅显示共享内存段的信息。
    • -q--queues:仅显示消息队列的信息。
    • -s--semaphores:仅显示信号量的信息。
    • -i <id>--identifier <id>:显示特定 IPC 资源 ID 的详细信息。
    • -l--limits:显示系统限制信息。
    • -u--summary:显示 IPC 资源的摘要信息。
    • -p--pid:显示创建 IPC 资源的进程 ID。
    • -t--time:显示最后操作 IPC 资源的时间。
    • -c--creator:显示 IPC 资源的创建者信息。
    • -h--human:以人类可读的格式显示大小。
    • -v--version:显示 ipcs 命令的版本信息。
    • -V--help:显示帮助信息。
  4. 示例
    • ipcs -a:显示所有类型的 IPC 资源信息。
    • ipcs -m:仅显示共享内存段的信息。
    • ipcs -q:仅显示消息队列的信息。
    • ipcs -s:仅显示信号量的信息。
    • ipcs -u:显示 IPC 资源的摘要信息。
    • ipcs -i 1234:显示 ID 为 1234 的 IPC 资源的详细信息。

shmat函数

  1. 原型

    #include <sys/shm.h>
    void *shmat(int shmid, const void *shmaddr, int shmflg);
    
  2. 功能:将创建好的共享内存段连接到某个进程,并指定内存空间

  3. 参数

    • shmid:是通过shmget函数返回的共享内存标识符,它唯一标识了一个共享内存段。
    • shmaddr:是一个可选参数,用于指定共享内存映射到进程地址空间中的起始地址。如果传递NULL,则由系统自动选择一个地址进行映射。
    • shmflg:是一组标志位,用于控制共享内存的访问权限和其他属性。常见的标志包括SHM_RDONLY(只读访问)和SHM_WRONLY(只写访问)。
  4. 返回值

    • 成功,返回一个指向共享内存段在进程地址空间中映射的起始位置的指针。这个指针可以用于访问共享内存中的数据。
    • 失败,返回-1

shmdt函数

  1. 原型

    #include <sys/shm.h>
    int shmdt(const void *shmaddr);
    
  2. 功能:从进程的地址空间中分离已经附加的共享内存段

  3. 参数shmaddr是一个指向共享内存段的指针,这个指针通常是通过shmat函数返回的。

  4. 返回值

    • 成功,shmdt函数返回0
    • 失败,返回 -1,并设置 errno 以指示错误原因。

shmctl函数

  1. 原型

    #include <sys/shm.h>
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    
  2. 功能:控制共享内存段,包括删除和修改权限等操作

  3. 参数

    • shmid:是通过shmget函数返回的共享内存标识符,唯一标识了一个共享内存段。
    • cmd:是控制命令,用于指定要执行的操作
      • IPC_STAT(获取共享内存的状态)
      • IPC_SET(设置共享内存的状态)
      • IPC_RMID(删除共享内存段)
    • buf:是一个指向shmid_ds结构体的指针,用于存储或接收共享内存的状态信息。在某些命令下,如IPC_STAT和IPC_SET,需要提供这个参数。
  4. 返回值

    • 成功,shmctl函数返回0;
    • 出错,返回 -1,并设置 errno 以指示错误原因

示例

创建和使用共享内存

write.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>int main() {key_t key;  // 定义一个键值变量int shmid;  // 定义一个共享内存标识符变量char *buf;  // 定义一个指向共享内存的指针变量key = ftok("keytest", 100);  // 生成一个唯一的键值if (key < 0) {perror("ftok");  return 0;}printf("key=%x\n", key);  // 打印生成的键值shmid = shmget(key, 512, IPC_CREAT | 0666);  //创建并获取共享内存段if (shmid < 0) {perror("shmget");  return 0;}printf("shmid=%d\n", shmid);  // 打印共享内存标识符buf = shmat(shmid, NULL, 0);  // 将共享内存段附加到进程的地址空间中if (buf < 0) {perror("shmat");  return 0;}strcpy(buf, "hello world");  // 将字符串"hello world"复制到共享内存中
}

read.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>int main() {key_t key;  // 定义一个键值变量int shmid;  // 定义一个共享内存标识符变量char *buf;  // 定义一个指向共享内存的指针变量key = ftok("keytest", 100);  // 生成一个唯一的键值if (key < 0) {perror("ftok");  return 0;}printf("key=%x\n", key);  // 打印生成的键值shmid = shmget(key, 512, 0666);  // 获取共享内存段if (shmid < 0) {perror("shmget");  return 0;}printf("shmid=%d\n", shmid);  // 打印共享内存标识符buf = shmat(shmid, NULL, 0);  // 将共享内存段附加到进程的地址空间中if (buf < 0) {perror("shmat");  return 0;}printf("read = %s\n",buf);if(shmdt(buf) == -1)//共享内存段从进程的地址空间中分离perror("shmdt");if(shmctl(shmid, IPC_RMID, NULL) == -1)//删除共享内存段perror("shmctl");return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/19830.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【GD32】05 - PWM 脉冲宽度调制

PWM PWM (Pulse Width Modulation) 是一种模拟信号电平的方法&#xff0c;它通过使用数字信号&#xff08;通常是方波&#xff09;来近似地表示模拟信号。在PWM中&#xff0c;信号的占空比&#xff08;即高电平时间占整个周期的比例&#xff09;被用来控制平均输出电压或电流。…

MFC 解决Enter回车键和Esc取消键默认关闭窗口的三种方法

文章目录 问题描述问题原因解决办法方法一&#xff1a;在重载的PreTranslateMessage 函数中屏蔽回车和ESC 的消息方法二&#xff1a;重载OnOK函数方法三&#xff1a;将所有按钮类型设为普通按钮&#xff0c;并设置其中一个按钮为默认按钮 问题描述 一般情况下编写的MFC对话框程…

HTML语义化标签

<header> 主要用于网页整体顶部&#xff0c;<article>头部&#xff0c;<section>头部 <nav> 导航&#xff0c;一般有主要导航&#xff0c;路径导航&#xff0c;章节导航&#xff0c;内容目录导航 <main> 网页主要区域&#xff0c;一般一个网页…

【运维项目经历|025】企业高效邮件系统部署与运维项目

目录 项目名称 项目背景 项目目标 项目成果 我的角色与职责 我主要完成的工作内容 本次项目涉及的技术 本次项目遇到的问题与解决方法 本次项目中可能被面试官问到的问题 经验教训与自我提升 展望未来 项目名称 企业高效邮件系统部署与运维项目 项目背景 随着企业…

AI之下 360让PC商业生态大象起舞

时隔7年&#xff0c;淘宝PC版在前不久迎来重磅升级&#xff0c;在产品体验、商品供给、内容供给等方面做了全面优化&#xff0c;以全面提升PC端的用户体验&#xff1b;当大家都以为移动互联网时代下APP将成为主流时&#xff0c;PC端却又成为了香饽饽。其实PC端被重视&#xff0…

3389,为了保障3389端口的安全,我们可以采取的措施

3389端口&#xff0c;作为远程桌面协议&#xff08;RDP&#xff09;的默认端口&#xff0c;广泛应用于Windows操作系统中&#xff0c;以实现远程管理和控制功能。然而&#xff0c;正因为其广泛使用&#xff0c;3389端口也成为许多潜在安全威胁的入口。因此&#xff0c;确保3389…

go 针对 time类型字段,前端查询,后端返回数据格式为UTC时间

测试代码 package mainimport ("context""log""net/http""time""github.com/gin-gonic/gin""go.mongodb.org/mongo-driver/bson""go.mongodb.org/mongo-driver/bson/primitive""go.mongodb.org/m…

鸿蒙ArkTS声明式开发:跨平台支持列表【显隐控制】 通用属性

显隐控制 控制组件是否可见。 说明&#xff1a; 开发前请熟悉鸿蒙开发指导文档&#xff1a; gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本…

docker部署owncloud进行管理

目录 一.拉取镜像 1.使用mysql和owncloud最新版镜像&#xff0c;构建个人网盘 2.查看是否已经正确监听端口 二.使用浏览器进行测试 1.使用IP:8080进行访问&#xff0c;用admin运行容器时设置的密码登录 2.查看到已经有的文件 3.文件上传对应的位置 4.在web页面进行简单…

liunx文件系统与日志分析

文章目录 一、基本概念二、日志分析三、实验 一、基本概念 文件是存储在硬盘上的&#xff0c;硬盘上的最小存储单位是扇区每个扇区大小事512字节 inode&#xff1a;元信息&#xff08;文件的属性 权限 创建者 创建日期&#xff09; block&#xff1a;块 连续八个扇区组成一块…

[RK3588-Android12] 关于BQ25703充电IC+CW2017电量计调试

问题描述 BQ25703充电ICCW2017电量计调试 解决方案&#xff1a; 附上dts配置文件 &i2c6 {clock-frequency <400000>;status "okay";// CONFIG_BATTERY_CW2017cw2017: cw201763 {status "okay";compatible "cellwise,cw2017";re…

Java操作Excel文档进行读取和写入

目录 读出Excel文档 写入Excel文档 读出Excel文档 使用EasyExcel读取Excel文件: 需要在maven项目中导入EasyExcel依赖 <!-- EasyExcel依赖包 --> <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><v…

长难句打卡5.31

In a workplace that’s fundamentally indifferent to your life and its meaning, office speak can help you figure out how you relate to your work—and how your work defines who you are. 在一个对你的生活和生活意义漠不关心的工作场所中&#xff0c;办公室语言可以…

揭秘蓝牙AOA定位系统:精准定位,开启智能导航新时代

随着科技的飞速发展&#xff0c;人们对于定位技术的需求也日益增长。在众多定位技术中&#xff0c;蓝牙AOA定位系统以其高精度、低通信开销的特点&#xff0c;逐渐受到广泛关注。接下来给大家简单介绍一下关于蓝牙AOA定位系统的原理、优势及应用场景&#xff0c;带大家领略其带…

情感读本期刊万方收录综合期刊投稿

《情感读本》杂志是由国家新闻出版总署批准&#xff0c;湖北省新闻出版广电局主管&#xff0c;湖北省期刊协会主办的正规综合类期刊。《情感读本》是一本以推动和发展情感教育、素质教育、人文教育为己任&#xff0c;奉行“立足教育&#xff0c;服务社会”的办刊宗旨&#xff0…

Jmeter性能测试-【关联,提取器】

新知识点 关联&#xff1a; 正则表达式提取器 边界提取器 XPath提取器 JSON提取器 梳理框架 1. Jmeter基础 定义&#xff1a;Jmeter是一个开源的性能测试工具&#xff0c;主要用于Web应用和各种服务的性能测试。 主要功能&#xff1a;可以模拟多用户并发访问&#xff0c;测…

Vue3兼容低版本浏览器(ie11,chrome63)

1、插件安装 为了使你的项目兼容 Chrome 63&#xff0c;你需要确保包含适当的 polyfills 和插件配置。你已经在使用 legacy 插件&#xff0c;但在代码中可能缺少一些配置或插件顺序有问题。以下是几个可能的改进&#xff1a; 安装 vitejs/plugin-legacy 插件&#xff1a; 确保…

(2024,Video2Game,NeRF,Mesh,物理模块,游戏引擎)通过单个视频实现实时、交互、逼真且兼容浏览器的环境

Video2Game: Real-time, Interactive, Realistic and Browser-Compatible Environment from a Single Video 公众号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 2. 相关工作 3. Video…

Mysql基础教程(12):JOIN

MySQL JOIN 在 MySQL 中&#xff0c;JOIN 语句用于将数据库中的两个表或者多个表组合起来。 比如在一个学校系统中&#xff0c;有一个学生信息表和一个学生成绩表。这两个表通过学生 ID 字段关联起来。当我们要查询学生的成绩的时候&#xff0c;就需要连接两个表以查询学生信…