当前位置: 首页 > news >正文

深入理解和实现Windows进程间通信(共享内存)

常见的进程间通信方法

常见的进程间通信方法有:

  1. 管道(Pipe)
  2. 消息队列
  3. 共享内存
  4. 信号量
  5. 套接字

下面,我们将详细介绍共享内存的原理以及具体实现。

什么是共享内存?

Windows共享内存(Shared Memory in Windows)是一种操作系统机制,允许不同的进程(程序)共享一段内存空间。这意味着多个进程可以同时访问同一个内存区域,用以交换数据或进行通信,这是进程间通信(IPC)的一种形式。使用共享内存通常可以提高应用程序之间的数据交换效率,因为它避免了数据的复制过程,直接在内存中进行读写。

共享内存的实现方式

在Windows系统中,共享内存的实现通常有以下几种方式:

  1. 内存映射文件
  • 这是最常见的实现共享内存的方式。通过将磁盘上的文件映射到内存地址空间,文件的内容可以被映射到多个进程的地址空间,从而实现共享。
  1. 命名管道
  • 虽然主要用于进程间的消息传递,命名管道也可以配置为在内存中传输数据,从而模拟共享内存的效果。
  1. 剪贴板
  • 剪贴板提供了一种将数据存储在共享内存中的方法,这样不同的程序可以访问和修改这些数据。
  1. 全局原子表
  • 全局原子表允许程序创建小段的、全局可访问的数据(原子),这些数据可由其他程序读取和修改。

本文只介绍内存映射文件这种方式。

内存映射文件原理

文件与内存的映射

内存映射文件通过将磁盘上的文件或一段虚拟内存与进程的地址空间进行映射来工作。这种映射实际上是创建了文件内容与进程虚拟地址空间之间的直接联系。

操作系统的角色

操作系统负责管理内存和磁盘文件之间的映射关系。当一个文件被映射到内存时,操作系统将文件的一部分或全部内容呈现为进程虚拟内存的一部分。这样,对这部分虚拟内存的访问就相当于直接读写文件内容。

虚拟内存管理

分页机制

  • 操作系统使用分页机制来管理物理内存和虚拟内存。内存映射文件利用这一机制,将文件的内容按页对应到虚拟内存页上。
  • 当进程访问这些虚拟页时,如果对应的物理页不在内存中(即页面错误),操作系统将从磁盘中加载所需的数据页到物理内存中。

写时复制(Copy-on-Write)

  • 对于共享内存映射,操作系统可能使用写时复制策略。这意味着当进程试图写入共享内存时,系统会为该进程创建这部分内存的私有副本,从而保护原始内存内容。

延迟加载

内存映射文件通常不会在映射时立即加载整个文件内容。而是采用延迟加载的方式,即只有在实际访问某个内存区域时,相应的文件部分才被加载到物理内存中。这样可以提高效率,减少内存消耗。

同步和一致性

操作系统还负责同步映射文件的内存视图和磁盘上的文件内容。当进程修改了映射的内存后,这些变更可能会延迟写回到磁盘文件中。这涉及到内存和磁盘操作的一致性和同步问题。

性能优势

内存映射文件提供了比传统的文件I/O更快的数据访问速度,因为它避免了多次的数据复制和用户空间与内核空间之间的上下文切换。数据直接在内存中修改,只在必要时进行磁盘I/O操作。

接口介绍

CreateFileMappint

功能

基于实际的磁盘文件或系统分页文件来创建内存映射文件对象。

声明

HANDLE CreateFileMapping(HANDLE hFile,LPSECURITY_ATTRIBUTES lpAttributes,DWORD flProtect,DWORD dwMaximumSizeHigh,DWORD dwMaximumSizeLow,LPCTSTR lpName
);

参数

  • hFile:文件句柄,INVALID_HANDLE_VALUE用于系统分页文件。
  • lpAttributes:安全属性,通常为NULL
  • flProtect:保护属性,读写权限
    • PAGE_READONLY:分配的页面只读
    • PAGE_READWRITE:分配的页面可读可写
    • PAGE_WRITECOPY:分配的页面写时复制,就是当多个进程映射到同一个内存区域进行读写操作时,它们实际上是在读取同一份数据的副本。但是,当任何一个进程尝试修改这些数据时,操作系统会为该进程创建这部分数据的私有副本,从而隔离修改操作,确保其他进程看到的数据仍然是未被修改的原始数据
    • PAGE_EXECUTE:分配的页面可执行,不可写入。这通常用于执行代码,而非存储数据
    • PAGE_EXECUTE_READ:分配的页面可执行和可读。这适用于执行某些代码,同时需要从相同的内存区域读取数据
    • PAGE_EXECUTE_READWRITE:分配的页面可执行、可读写。这是最灵活的权限,允许执行代码并修改数据
    • PAGE_EXECUTE_WRITECOPY:分配的页面可执行、可读、且写时复制。对这些页面的写入不会影响到原始数据或其他映射的视图
  • dwMaximumSizeHigh: 映射对象的最大大小(高32位)
  • dwMaximumSizeLow: 映射对象的最大大小(低32位)
  • lpName: 映射对象的名称,可用于进程间共享

OpenFileMapping

功能

用于打开一个已经存在的内存映射文件对象,通常在不同的进程中使用,以访问由CreateFileMapping创建的共享内存区域。

声明

HANDLE OpenFileMapping(DWORD dwDesiredAccess,BOOL bInheritHandle,LPCTSTR lpName
);

参数

  • dwDesiredAccess:访问文件映射的权限,需要和CreateFileMapping设置的权限匹配
    • FILE_MAP_ALL_ACCESS:请求完全访问权限,包括读、写和执行
    • FILE_MAP_READ:请求读权限
    • FILE_MAP_WRITE:请求写权限
    • FILE_MAP_COPY:请求写时复制权限
    • FILE_MAP_EXECUTE:请求执行权限
  • bInheritHandle:句柄是否可以被子进程继承
  • lpName:映射对象的名称

MapViewOfFile

功能

将一个文件映射对象映射到调用进程的地址空间,使得文件内容可以通过指针访问。

声明

LPVOID MapViewOfFile(HANDLE hFileMappingObject,DWORD dwDesiredAccess,DWORD dwFileOffsetHigh,DWORD dwFileOffsetLow,SIZE_T dwNumberOfBytesToMap
);

参数

  • hFileMappingObject:共享内存对象的句柄
  • dwDesiredAccess:访问类型,参考OpenFileMapping函数的第一个参数
  • dwFileOffsetHigh: 映射视图的文件偏移量(高32位)
  • dwFileOffsetLow: 映射视图的文件偏移量(低32位)
  • dwNumberOfBytesToMap: 映射的字节数,0表示从偏移量到文件末尾

UnmapViewOfFile

功能

断开文件映射对象和调用进程地址空间之间的映射关系。这是在映射后,清理资源前的必要步骤。

声明

BOOL UnmapViewOfFile(LPCVOID lpBaseAddress
);

参数

  • lpBaseAddressMapViewOfFile返回的基地址

CloseHandle

功能

关闭句柄。使用完映射对象后,应关闭这些句柄以释放资源。

声明

BOOL CloseHandle(HANDLE hObject
);

参数

  • hObject:要关闭的对象句柄

实现

本文将实现两个进程,进程1创建共享内存,并一直更新数据,进程2从共享内存中读取数据并打印输出。

进程1代码

#pragma once
#include <windows.h>
#include <iostream>
#include <string>int sharedMemoryImpl() {// 创建或打开一个命名的内存映射文件对象HANDLE hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE,    // 使用系统分页文件NULL,                    // 默认安全性PAGE_READWRITE,          // 可读写权限0,                       // 最大对象大小(高位)256,                     // 最大对象大小(低位)L"Local\\MySharedMemory"); // 名称if (hMapFile == NULL) {std::cerr << "Could not create file mapping object: " << GetLastError();return 1;}// 映射缓冲区视图char* pBuf = (char*)MapViewOfFile(hMapFile, // 映射对象句柄FILE_MAP_ALL_ACCESS,  // 可读写许可0,0,256);                  // 映射大小if (pBuf == NULL) {std::cerr << "Could not map view of file: " << GetLastError();CloseHandle(hMapFile);return 1;}// 创建事件对象用于同步HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, L"Local\\MySharedEvent");if (hEvent == NULL) {std::cerr << "Could not create event object: " << GetLastError();UnmapViewOfFile(pBuf);CloseHandle(hMapFile);return 1;}// 写入初始数据CopyMemory(pBuf, "Hello, number 1", 15);int number = 1;while (true) {// 增加数字并更新内存sprintf_s(pBuf, 256, "Hello, number %d", ++number);std::cout << "Data written to memory: " << pBuf << std::endl;// 通知进程2SetEvent(hEvent);// 等待1秒Sleep(1000);}// 清理UnmapViewOfFile(pBuf);CloseHandle(hMapFile);CloseHandle(hEvent);return 0;
}

进程2代码

#pragma once
#include <windows.h>
#include <iostream>int sharedMemoryImpl() {// 打开映射文件对象HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS,   // 最大访问权限FALSE,                 // 继承性标志L"Local\\MySharedMemory");  // 对象名称if (hMapFile == NULL) {std::cerr << "Could not open file mapping object: " << GetLastError();return 1;}// 映射缓冲区视图char* pBuf = (char*)MapViewOfFile(hMapFile, // 映射对象句柄FILE_MAP_ALL_ACCESS,  // 访问模式0,0,256);                  // 视图大小if (pBuf == NULL) {std::cerr << "Could not map view of file: " << GetLastError();CloseHandle(hMapFile);return 1;}// 打开事件对象HANDLE hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, L"Local\\MySharedEvent");if (hEvent == NULL) {std::cerr << "Could not open event: " << GetLastError();UnmapViewOfFile(pBuf);CloseHandle(hMapFile);return 1;}while (true) {// 等待事件WaitForSingleObject(hEvent, INFINITE);// 从共享内存读取数据并打印std::cout << "Data read from memory: " << pBuf << std::endl;}// 清理UnmapViewOfFile(pBuf);CloseHandle(hMapFile);CloseHandle(hEvent);return 0;
}

结果

Video_2024-06-21_102211.gif

相关文章:

  • 防火墙规则来阻止攻击者的 IP 地址
  • Vim入门教程
  • 9、PHP 实现调整数组顺序使奇数位于偶数前面
  • 【CT】LeetCode手撕—300. 最长递增子序列
  • 手机在网状态-手机在网状态查询-手机在网站状态接口
  • wsl2平台鸿蒙全仓docker编译环境快速创建方法
  • Spring自定义标签体系和应用
  • 嵌入式软件stm32面试
  • 如何减少sql出现问题
  • MacOS设备远程登录配置结合内网穿透实现异地ssh远程连接
  • k8s及etcd的每日自动备份及故障时的还原脚本
  • windows环境下,怎么查看本机的IP、MAC地址和端口占用情况
  • day64 图论 图论理论基础 深搜 广搜 98. 所有可达路径
  • Java学习 - MySQL视图的练习 实例
  • R语言——数据与运算
  • 【node学习】协程
  • HTML5新特性总结
  • If…else
  • isset在php5.6-和php7.0+的一些差异
  • leetcode98. Validate Binary Search Tree
  • nginx(二):进阶配置介绍--rewrite用法,压缩,https虚拟主机等
  • Otto开发初探——微服务依赖管理新利器
  • python docx文档转html页面
  • Python 基础起步 (十) 什么叫函数?
  • react-core-image-upload 一款轻量级图片上传裁剪插件
  • Xmanager 远程桌面 CentOS 7
  • 多线程事务回滚
  • 官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?
  • 基于Vue2全家桶的移动端AppDEMO实现
  • 判断客户端类型,Android,iOS,PC
  • 巧用 TypeScript (一)
  • 深入浅出Node.js
  • 什么软件可以剪辑音乐?
  • 突破自己的技术思维
  • 一加3T解锁OEM、刷入TWRP、第三方ROM以及ROOT
  • 【运维趟坑回忆录】vpc迁移 - 吃螃蟹之路
  • ​Java基础复习笔记 第16章:网络编程
  • #我与Java虚拟机的故事#连载17:我的Java技术水平有了一个本质的提升
  • (22)C#传智:复习,多态虚方法抽象类接口,静态类,String与StringBuilder,集合泛型List与Dictionary,文件类,结构与类的区别
  • (30)数组元素和与数字和的绝对差
  • (js)循环条件满足时终止循环
  • (ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY)讲解
  • (六)激光线扫描-三维重建
  • (七)MySQL是如何将LRU链表的使用性能优化到极致的?
  • (十五)devops持续集成开发——jenkins流水线构建策略配置及触发器的使用
  • (四)opengl函数加载和错误处理
  • (原創) 如何動態建立二維陣列(多維陣列)? (.NET) (C#)
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • .mp4格式的视频为何不能通过video标签在chrome浏览器中播放?
  • .NET 4.0网络开发入门之旅-- 我在“网” 中央(下)
  • .net php 通信,flash与asp/php/asp.net通信的方法
  • .NET性能优化(文摘)
  • .NET中 MVC 工厂模式浅析
  • ?php echo ?,?php echo Hello world!;?
  • @Slf4j idea标红Cannot resolve symbol ‘log‘