【Linux系统编程】共享内存

devtools/2025/3/16 15:35:26/

目录

    • 1、什么是共享内存
    • 2、mmap函数
    • 3、mmap对比read、write
    • 4、munmap 函数
    • 5、msync函数
      • 5.1、通过页表机制,将修改的数据同步到磁盘,那为什么还需要msync函数
    • 6、示例
      • 6.1、使用mmap让进程之间通信,文件作为媒介
      • 6.3、修改文件,使用msync显示同步到磁盘
    • 7、shmget shmat shmdt shmctl实现系统V共享内存
      • 7.1、`shmget()` 函数
      • 7.2 、shmat() 函数
      • 7.3、shmdt() 函数
      • 7.4、shmctl() 函数
      • 7.5、示例

1、什么是共享内存

共享内存(Shared Memory)是一种高效的 进程间通信(IPC, Inter-Process Communication) 机制,它允许多个进程共享同一块内存区域。通过共享内存,进程可以直接读写同一块内存,避免了数据复制的开销,因此速度非常快。

2、mmap函数

头文件

#include <sys/mman.h>

函数定义

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

参数说明

addr

  • 指定映射的起始地址。通常设置为 NULL,由内核自动选择合适的位置。

length

  • 映射区域的长度(以字节为单位)。

prot

  • 指定内存保护标志,控制进程对映射区域的访问权限。可以是以下值的组合:
    • PROT_READ:可读。
    • PROT_WRITE:可写。
    • PROT_EXEC:可执行。
    • PROT_NONE:不可访问。

flags

  • 控制映射的行为。可以是以下值的组合:
    • MAP_SHARED:共享映射,对映射区域的修改会反映到文件中。
    • MAP_PRIVATE:私有映射,对映射区域的修改不会反映到文件中。
    • MAP_ANONYMOUS:匿名映射,不关联任何文件。
    • MAP_FIXED:强制使用指定的起始地址。

fd

  • 文件描述符,指定要映射的文件。如果使用 MAP_ANONYMOUS,则设置为 -1。

offset

  • 文件中的偏移量,表示从文件的哪个位置开始映射。通常设置为 0。

返回值

  • 成功时,返回映射区域的起始地址。
  • 失败时,返回 MAP_FAILED(即 (void *)-1),并设置 errno。

mmap 的常见用途

1、文件映射
将文件映射到内存中,直接通过内存访问文件内容,适合处理大文件或需要频繁访问的文件。

2、共享内存
使用 MAP_SHARED 标志创建共享内存,多个进程可以共享同一块内存区域。

3、匿名映射
使用 MAP_ANONYMOUS 创建匿名映射,用于动态分配内存或实现进程间的共享内存。
在这里插入图片描述

3、mmap对比read、write

通过mmap()操作文件比read()、write()要快,因为read()、write()会拷贝到缓冲区中,而mmap()不用,直接写到文件中
read() write()的工作流程

read()

  • 内核从磁盘读取数据到内核缓冲区。
  • 数据从内核缓冲区拷贝到用户空间缓冲区。
  • 应用程序访问用户空间缓冲区中的数据。

write()

  • 应用程序将数据写入用户空间缓冲区。
  • 数据从用户空间缓冲区拷贝到内核缓冲区。
  • 内核将数据从内核缓冲区写入磁盘。

在这个过程中,数据需要在内核缓冲区和用户空间缓冲区之间进行两次拷贝,这会增加额外的开销。


mmap() 的工作流程

映射文件:

  • 使用 mmap() 将文件直接映射到进程的地址空间。 文件的内容被映射到虚拟内存中,进程可以通过指针直接访问。

读写操作:

  • 进程直接通过指针读写映射的内存区域。 操作系统负责将修改的数据同步到磁盘(通过页表机制)。

在这个过程中,数据不需要在用户空间和内核空间之间拷贝,因此减少了额外的开销。


mmap优势
1、减少数据拷贝:
mmap() 避免了 read() 和 write() 中的两次数据拷贝,直接通过内存映射访问文件数据。

2、高效访问大文件:
对于大文件,mmap() 可以按需加载文件内容(通过页表机制),而不需要一次性将整个文件加载到内存中。

3、简化编程:
使用 mmap() 后,文件数据可以直接通过指针访问,编程更加直观。

4、共享内存:
多个进程可以映射同一个文件,实现高效的进程间通信。


mmap的适用场景

1、大文件处理:
当需要处理大文件时,mmap() 可以按需加载数据,减少内存占用。

2、频繁随机访问:
如果需要对文件进行频繁的随机访问,mmap() 比 read() 和 write() 更高效。

3、进程间通信:
多个进程可以通过映射同一个文件实现共享内存。


mmap 的局限性

内存映射大小:

  • mmap() 映射的文件大小受限于虚拟地址空间的大小。

同步开销:

  • 频繁修改映射区域时,操作系统需要将修改的数据同步到磁盘,可能会带来额外的开销。

复杂性:

  • 使用 mmap() 需要处理页表、内存对齐等问题,编程复杂度较高。

4、munmap 函数

munmap 用于解除内存映射,释放映射区域。
头文件

#include <sys/mman.h>

函数定义

int munmap(void *addr, size_t length);

参数说明

  • addr 映射区域的起始地址。
  • length 映射区域的长度。

返回值

  • 成功时,返回 0。
  • 失败时,返回 -1,并设置 errno。

5、msync函数

功能:
msync 是一个在 Unix/Linux 系统中用于同步内存映射文件(memory-mapped file)与磁盘文件的系统调用函数。它的主要作用是将内存中对文件的修改写回到磁盘,或者从磁盘重新加载文件内容到内存,以确保内存和磁盘之间的数据一致性。

头文件

#include <sys/mman.h>

函数定义

int msync(void *addr, size_t length, int flags);

参数说明

  • addr: 指向内存映射区域的起始地址。
  • length: 需要同步的内存区域的长度。
  • flags: 控制同步行为的标志,常用的标志包括:
    • MS_SYNC: 同步写操作,确保数据完全写入磁盘后才返回。
    • MS_ASYNC: 异步写操作,立即返回,不等待数据写入磁盘。
    • MS_INVALIDATE: 使缓存失效,强制从磁盘重新加载数据。

返回值

  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno 来指示错误类型。

5.1、通过页表机制,将修改的数据同步到磁盘,那为什么还需要msync函数

一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

msync() 函数的作用是显式地将内存映射区域(mmap()
映射的文件)的修改同步到磁盘。虽然操作系统确实会通过页表机制将修改的数据同步到磁盘,但这种同步通常是延迟的,而不是立即执行的

为什么需要 msync()?

1、延迟写入(Lazy Write):
操作系统为了提高性能,通常会延迟将修改的数据写入磁盘。修改的数据可能暂时保留在内存的页缓存中,直到满足以下条件之一:

  • 内存不足,需要回收页缓存。
  • 系统调用 sync() 或 fsync() 被触发。
  • 文件被关闭或取消映射(munmap())。

这种延迟写入机制可能导致数据在系统崩溃或断电时丢失。

2、显式控制同步时机:

  • 使用 msync() 可以让应用程序显式控制何时将修改的数据写入磁盘,确保数据的持久性。
  • 例如,在关键数据修改完成后立即调用 msync(),可以避免数据丢失的风险。

3、部分同步:
msync() 允许只同步内存映射区域的某一部分(通过指定地址和长度),而不是整个映射区域。这在处理大文件时可以提高效率。

4、跨进程共享映射:
如果多个进程共享同一个文件映射,一个进程修改数据后,其他进程可能无法立即看到修改结果。调用 msync() 可以确保修改的数据对其他进程可见。


6、示例

6.1、使用mmap让进程之间通信,文件作为媒介

写入文件

#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>int main()
{//打开/建立文件char *filepath = "hello.txt";int fd = open(filepath,O_RDWR | O_CREAT,0666);if(fd < 0){perror("open file error");return -1;}//创建空洞文件if(ftruncate(fd,1024) < 0){perror("ftruncate error");close(fd);return -1;}//创建共享内存,并关联fd文件(建立好映射关系)void *addr = mmap(NULL,1024,PROT_WRITE,MAP_SHARED,fd,0);if(addr == (void*)-1){perror("mmap error");close(fd);return -1;}char *str = "abc";strcpy(addr,str);  //实现地址到地址的拷贝,比write效率更高//解除映射munmap(addr,1024);close(fd);}

从文件中读出

#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>int main()
{//打开/建立文件char *filepath = "hello.txt";int fd = open(filepath,O_RDWR | O_CREAT,0666);if(fd < 0){perror("open file error");return -1;}//创建共享内存,并关联fd文件(建立好映射关系)void *addr = mmap(NULL,1024,PROT_READ,MAP_SHARED,fd,0);if(addr == (void*)-1){perror("mmap error");close(fd);return -1;}char buf[128] = "";strncpy(buf,addr,sizeof(buf));printf("buf = %s\n",buf);//解除映射munmap(addr,1024);close(fd);}

6.3、修改文件,使用msync显示同步到磁盘

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>int main() {int fd = open("test.txt", O_RDWR);struct stat sb;fstat(fd, &sb);// 映射文件到内存char *ptr = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);// 修改文件内容strcpy(ptr, "Hello, mmap!");// 显式同步到磁盘if (msync(ptr, sb.st_size, MS_SYNC) == -1) {perror("msync failed");}// 取消映射munmap(ptr, sb.st_size);close(fd);return 0;
}

7、shmget shmat shmdt shmctl实现系统V共享内存

7.1、shmget() 函数

用于创建或获取一个共享内存段。

#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);

参数:

  • key:共享内存段的键值。通常使用 ftok() 生成,或使用 IPC_PRIVATE 创建一个新的共享内存段。
  • size:共享内存段的大小(字节)。
  • shmflg:标志位,指定共享内存段的创建方式和权限。常用标志:
    • IPC_CREAT:如果共享内存段不存在,则创建它。
    • IPC_EXCL:与 IPC_CREAT 一起使用,如果共享内存段已存在,则返回错误。
    • 权限模式:如 0666,表示所有用户可读写。

返回值:

  • 成功:返回共享内存段的标识符(shmid)。
  • 失败:返回 -1,并设置 errno。

7.2 、shmat() 函数

将共享内存段映射到进程的地址空间。

#include <sys/types.h>
#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);

参数:

  • shmid:共享内存段的标识符(由 shmget() 返回)。
  • shmaddr:指定映射地址。通常为 NULL,由系统自动选择。
  • shmflg:标志位。常用标志:
    • SHM_RDONLY:以只读方式映射共享内存。

返回值:

  • 成功:返回共享内存段的映射地址。
  • 失败:返回 (void *)-1,并设置 errno。

7.3、shmdt() 函数

将共享内存段从进程地址空间中解除映射。

#include <sys/types.h>
#include <sys/shm.h>int shmdt(const void *shmaddr);

参数:

  • shmaddr:共享内存段的映射地址(由 shmat() 返回)。

返回值:

  • 成功:返回 0。
  • 失败:返回 -1,并设置 errno。

7.4、shmctl() 函数

控制共享内存段(如删除或获取信息)。

#include <sys/ipc.h>
#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数:

  • shmid:共享内存段的标识符(由 shmget() 返回)。
  • cmd:控制命令。常用命令:
    • IPC_RMID:删除共享内存段。
    • IPC_STAT:获取共享内存段的状态信息,并存储到 buf 中。
    • IPC_SET:设置共享内存段的状态信息。
  • buf:指向 shmid_ds 结构体的指针,用于存储或设置共享内存段的信息。

返回值:

  • 成功:返回 0。
  • 失败:返回 -1,并设置 errno。

注意:共享内存不会随着程序结束而自动消除,而且数据也在,要么调用shmctl删除,要么自己用手敲命令去删除,否则永远留在系统中。

7.5、示例

#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>int main()
{//获取IPC标识符key_t key = ftok(".",'a');if(key < 0){perror("ftok error");return -1;}//创建共享内存int shmid = shmget(key,1024,0666 | IPC_CREAT);if(shmid < 0){perror("shmget error");return -1;}//建立共享内存的映射关系void *addr = shmat(shmid,NULL,0);if(addr == (void *)-1){perror("shmat error");return -1;}//写char *str = "hello";strcpy(addr,str);//读char buf[128] = "";strncpy(buf,addr,sizeof(buf));printf("buf = %s\n",buf);//解除映射关系if(shmdt(addr) < 0){perror("shmdt error");return -1;}//删除共享内存if(shmctl(shmid,IPC_RMID,NULL) < 0){perror("shmctl rm error");return -1;}
}

http://www.ppmy.cn/devtools/167593.html

相关文章

BGP路由聚合

BGP路由聚合 路由聚合解决了两类问题,一是减轻了设备传输和计算路由所需资源的负担,二是隐藏了具体的路由信息,减少了路由震荡的影响。 聚合方式相关命令备注静态聚合network配置静态路由汇总明确路由,再由network宣告自动聚合summary automatic对引入的路由进行主类掩码聚…

如何在需求分析阶段考虑未来扩展性

在需求分析阶段考虑未来扩展性的关键在于 前瞻规划、灵活架构、标准设计。其中&#xff0c;前瞻规划尤为重要&#xff0c;因为通过全面分析业务发展趋势与技术演进&#xff0c;能够在初期设计阶段预留足够扩展空间&#xff0c;降低后期改造成本&#xff0c;为企业长期发展奠定坚…

UE5与U3D引擎对比分析

Unreal Engine 5&#xff08;UE5&#xff09;和Unity 3D&#xff08;U3D&#xff09;是两款主流的游戏引擎&#xff0c;适用于不同类型的项目开发。以下是它们的主要区别&#xff0c;分点整理&#xff1a; 1. 核心定位 UE5&#xff1a; 主打3A级高画质项目&#xff08;如主机/P…

结合使用 OpenCV 和 TensorFlow进行图像识别处理

在实际项目中&#xff0c;OpenCV 和 TensorFlow 可以结合使用&#xff0c;发挥各自的优势。以下是一个典型的结合使用流程&#xff1a; 典型应用场景 &#xff08;1&#xff09;​人脸检测和识别 使用 OpenCV 进行人脸检测。使用 TensorFlow 进行人脸识别。 &#xff08;2&…

XSS总结

文章目录 原理解析&#xff1a;触发方式文件内容中的xss文件名中的xssHTTP请求中的xss其他 分类&#xff1a;根据攻击脚本存储的方式根据脚本是否通过服务器处理根据持久性 常见的js触发标签无过滤情况有过滤情况 xss-labs通关level1-level10level11-level20 XSS&#xff08;Cr…

【DeepSeek】蓝耘智算 | 中国AI新范式:蓝耘智算云+DeepSeek R1部署实战教程

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈人工智能与大模型应用 ⌋ ⌋ ⌋ 人工智能&#xff08;AI&#xff09;通过算法模拟人类智能&#xff0c;利用机器学习、深度学习等技术驱动医疗、金融等领域的智能化。大模型是千亿参数的深度神经网络&#xff08;如ChatGPT&…

TensorFlow 是什么?

TensorFlow 是一个由 Google 开发的开源机器学习框架&#xff0c;它提供了丰富的工具和库用于构建和训练各种机器学习模型。以下是 TensorFlow 的基本概念和使用场景&#xff1a; 基本概念&#xff1a; 张量&#xff08;Tensors&#xff09;&#xff1a;在 TensorFlow 中&…

HTML块级元素和内联元素(简单易懂)

在HTML中&#xff0c;元素可以分为块级元素&#xff08;Block-level elements&#xff09;和内联元素&#xff08;Inline elements&#xff09;。这两类元素在页面布局和样式应用上有不同的特点和用途。 一、块级元素&#xff08;Block-level elements&#xff09; 1. 定义 …