【并发程序设计】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.rhkb.cn/news/336977.html

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

相关文章

py黑帽子学习笔记_web攻击

python网络库 py2的urllib2 py3好像把urllib2继承到了标准库urllib&#xff0c;直接用urllib就行&#xff0c;urllib2在urllib里都有对应的接口 py3的urllib get请求 post请求&#xff0c;和get不同的是&#xff0c;先把post请求数据和请求封装到request对象&#xff0c;再…

亚马逊云科技专家分享 | OPENAIGC开发者大赛能量加油站6月5日场预约开启~

由联想拯救者、AIGC开放社区、英特尔联合主办的“AI生成未来第二届拯救者杯OPENAIGC开发者大赛”自上线以来&#xff0c;吸引了广大开发者的热情参与。 为了向技术开发者、业务人员、高校学生、以及个体创业人员等参赛者们提供更充分的帮助与支持&#xff0c;AIGC开放社区特别…

lua 计算第几周

需求 计算当前赛季的开始和结束日期&#xff0c;2024年1月1日周一是第1周的开始&#xff0c;每两周是一个赛季。 lua代码 没有处理时区问题 local const 24 * 60 * 60 --一整天的时间戳 local server_time 1716595200--todo:修改服务器时间 local date os.date("*t…

解析前端开发中同源策略与配置代理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 在前端开发中&#xff0c;跨域请求是一个常见的问题。同源策略限制了浏览器中一个页面…

2024中青杯数学建模竞赛A题人工智能视域下养老辅助系统的构建思路代码论文分析

2024中青杯数学建模A题论文和代码已完成&#xff0c;代码为A题全部问题的代码&#xff0c;论文包括摘要、问题重述、问题分析、模型假设、符号说明、模型的建立和求解&#xff08;问题1模型的建立和求解、问题2模型的建立和求解、问题3模型的建立和求解&#xff09;、模型的评价…

使用 Django Model 构建强大的数据库模型

文章目录 创建一个简单的 Django Model迁移数据库使用 Django Shell 操作模型Django Admin结论 在 Django 中&#xff0c;Model 是构建数据库模型的基础。它允许开发人员定义数据的结构&#xff0c;并提供了方便的方式来与数据库进行交互。本文将介绍如何使用 Django Model 来创…

光环云携手火山引擎共推全栈AI服务,赋能千行百业智能化转型,助力新质生产力发展

5月15日&#xff0c;2024春季火山引擎FORCE原动力大会在北京举办。作为智算云网综合服务提供商&#xff0c;光环云受邀出席大会&#xff0c;与火山引擎共同探索大模型时代下行业发展的新趋势。 会上&#xff0c;光环云数据有限公司正式与火山引擎签署生态伙伴合作协议&#xf…

Goby 漏洞发布|万户ezEIP企业管理系统 /member/success.aspx 命令执行漏洞

漏洞名称&#xff1a;万户ezEIP企业管理系统 /member/success.aspx 命令执行漏洞 English Name&#xff1a;Wanhu-ez-EIP /member/success.aspx Command Execution Vulnerability CVSS core: 9.0 影响资产数&#xff1a;6175 漏洞描述&#xff1a; 万户ezEIP是一种企业资源…

服务器内存与CPU要占用多少才合理?

一 通常服务器内存占用多少合理&#xff1f;cpu占用多少才合理&#xff1f; 1 通常配置范围建议&#xff1a; 建议CPU使用率不高于80%&#xff1b;内存使用率不高于80%&#xff1b; 注意&#xff1a;具体情况还需要根据服务器的实际负载和应用场景来判断。 2 内存使用率&…

【Linux系列】深入解析 `kill` 命令:Linux 下的进程管理利器

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Java的JDBC编程

博主主页: 码农派大星. 数据结构专栏:Java数据结构 数据库专栏:MySQL数据库 关注博主带你了解更多数据结构知识 1. Java的数据库编程&#xff1a;JDBC 数据库驱动包&#xff1a;不同的数据库&#xff0c;对应不同的编程语言提供了不同的数据库驱动包&#xff0c;如&#xff1…

JDK JRE JVM 三者的关系

总结&#xff1a; 1. jdk 中 的 javac 编译器将 .java 文件编译为 .class 字节码文件 &#xff08;编译&#xff09; 2. jre 执行 .class 字节码文件 &#xff08;运行&#xff09; 3. jre 通过 jvm 运行程序&#xff0c;确保程序能够在不同平台上正确执行&#xff08;实现跨平…

UE5 双手握剑的实现(逆向运动学IK)

UE5 双手握剑的实现 IK 前言 什么是IK&#xff1f; UE官方给我们提供了很多对于IK处理的节点&#xff0c;比如ABRIK、Two Bone IK、Full Body IK 、CCD IK等&#xff0c;但是看到这&#xff0c;很多人就好奇了&#xff0c;什么是IK&#xff1f; 首先我们来看看虚幻小白人的骨…

使用KEPServer连接欧姆龙PLC获取对应标签数据(标签值类型改为字符串型)

1.创建通道&#xff08;通道&#xff09;&#xff0c;&#xff08;选择对应的驱动&#xff0c;跟当前型号PLC型号对应&#xff09;。 2.创建设备&#xff0c;&#xff08;填入IP地址以及欧姆龙的默认端口号&#xff1a;44818&#xff09; 3.创建对应的标签。这里关键讲诉下字…

[Algorithm][动态规划][子序列问题][最长递增子序列][摆动序列]详细讲解

目录 0.子序列 vs 子数组1.最长递增子序列1.题目链接2.算法原理详解3.代码实现 2.摆动序列1.题目链接2.题目链接3.代码实现 0.子序列 vs 子数组 子序列&#xff1a; 相对顺序是跟源字符串/数组是一致的但是元素和元素之间&#xff0c;在源字符串/数组中可以是不连续的一般时间…

14-alert\confirm\prompt\自定义弹窗

一、认识alert\confirm\prompt 下图依次是alert、confirm、prompt&#xff0c;先认清楚长什么样子&#xff0c;以后遇到了就知道如何操作了。 二、alert操作 先用driver.switch_to.alert方法切换到alert弹出框上&#xff1b;可以用text方法获取弹出的文本信息&#xff1b;acce…

【知识拓展】机器学习基础(二):什么是模型、自定义模型、模型训练、模型调优

前言 接上文&#xff0c;前文对模型没有过多介绍&#xff0c;随着看的资料增多&#xff0c;对模型有了更多的自我认识&#xff0c;记录一下。要了解模型&#xff0c;我们先从零开始创建一个模型开始&#xff1a; 最简单的方法是使用Python和scikit-learn库。关于scikit-learn库…

Python面向对象学习笔记

Python面向对象编程 记录人&#xff1a; 李思成 时间&#xff1a; 2024/05/01至2024/05/23 课程来源&#xff1a; B站Python面向对象 1.面向对象编程概述 官方概述 程序是指令的集合&#xff0c;运行程序时&#xff0c;程序中的语句会变成一条或多条指令&#xff0c;然后…

Unity 生成模版代码

1、创建模版代码文本 using System.Collections; using System.Collections.Generic; using UnityEngine;public class ClassNameScritpItem : MonoBehaviour {public GameObject go;// Start is called before the first frame updatevoid Start(){go new GameObject();}// …

C++ vector的使用和简单模拟实现(超级详细!!!)

目录 前言 1.STL是什么 2.vector使用 2.1 vector简介 2.2 常用接口函数 1. 构造函数 2.operator[ ]和size&#xff0c;push_back 3. 用迭代器进行访问和修改 4. 范围for遍历 5.修改类型函数 pop_back find insert erase 6. 容量相关函数capacity resize reserve 3.…