相关文章推荐
文质彬彬的斑马  ·  C# ...·  1 年前    · 
高大的大象  ·  c++ convert windows ...·  1 年前    · 

1. 共享内存的简单介绍

共享内存就是允许两个不相关的进程访问同一个虚拟内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

共享内存,存在于每个进程的进程地址空间中,属于每个进程,由于它并不需要系统调用干预和数据复制,它的效率是非常高的,它比我们所学的几种IPC机制(信号量,管道,消息队列)都要快。虽然它性能最好,但是它不提供同步互斥机制,因此需要我们程序员来提供,带来了编程的难度。这也是其他IPC机制存在的原因~

总结以上我们可以得出下面3点:

1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。

2)为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一块内存而不需要进行数据的拷贝,从而大大提高效率。

3)由于多个进程共享一段内存,因此也需要依靠某种同步机制

2. 共享内存涉及的函数

使用共享内存主要用到四个函数。下面是这些函数的原型和使用说明。

1)CreateFileMapping

函数功能: 创建一个新的文件映射内核对象。即创建内存映射文件,将上述真正存在的物理文件映射成为一个虚拟的映射文件,就是把物理文件与虚拟文件绑定。

函数原型:

HANDLE CreateFileMapping(
  HANDLE hFile,                       //物理文件句柄
  LPSECURITY_ATTRIBUTES lpAttributes, //安全设置
  DWORD flProtect,                    //保护设置
  DWORD dwMaximumSizeHigh,            //高位文件大小
  DWORD dwMaximumSizeLow,             //低位文件大小
  LPCTSTR lpName                      //共享内存名称

参数说明:

hFile:任何可以获得的物理文件句柄。

a)如果你需要创建一个物理文件无关的内存映射也无妨, 将它设置成为 0xFFFFFFFF(INVALID_HANDLE_VALUE)就可以了。使用 INVALID_HANDLE_VALUE还需要设置需要申请的内存空间的大小, 无论物理文件句柄参数是否有效, 这样 CreateFileMapping 就可以创建一个和物理文件大小无关的内存空间给你, 甚至超过实际文件大小, 如果你的物理文件有效, 而大小参数为0, 则返回给你的是一个和物理文件大小一样的内存空间地址范围.  返回给你的文件映射地址空间是可以通过复制, 集成或者命名得到, 初始内容为0。

b) 如果需要和物理文件关联, 要确保你的物理文件创建的时候的访问模式和"保护设置"匹配, 比如: 物理文件只读, 内存映射需要读写就会发生错误. 推荐你的物理文件使用独占方式创建。(调用CreateFile函数创建,将其返回值赋值给hFile即可)

lpAttributes:安全设置,一般设置NULL就可以了, 使用默认的安全配置。

flProtect:当文件映射时读写文件的属性。其值为下述常数之一:

PAGE_READONLY以只读方式打开映射
PAGE_READWRITE 以可读、可写方式打开映射
PAGE_WRITECOPY 为写操作留下备份

或组合使用下述一个或多个常数:

SEC_COMMIT 为文件映射一个小节中的所有页分配内存
SEC_IMAGE  文件是个可执行文件
SEC_RESERVE  为没有分配实际内存的一个小节保留虚拟内存空间

PS:(⊙o⊙)…第二个表格的几个常数没用过,好像也不常用。我不太懂...

dwMaximumSizeHigh:文件映射的最大长度的高32位。(这个应该是与申请的内存空间大小相关)

dwMaximumSizeLow:文件映射的最大长度的低32位。如这个参数和dwMaximumSizeHigh都是零,就用磁盘文件的实际长度。一般将dwMaximumSizeHigh设置为0,dwMaximumSizeLow设置为我们想开辟的内存字节数。

lpName:指定文件映射对象的名字。如存在这个名字的一个映射,函数就会打开它。若为空,则创建一个无名的文件映射。

函数返回值:

a)如果函数成功,则返回值是新创建的文件映射对象的句柄。

b)如果在函数调用之前存在该对象,则该函数返回现有对象的句柄(具有当前大小,而不是指定大小),并且GetLastError返回ERROR_ALREADY_EXISTS。

c)如果函数失败,则返回值为NULL。 要获取扩展错误信息,请调用GetLastError。

调用CreateFileMapping的时候可能会出现的GetLastError的相应错误:

a)ERROR_FILE_INVALID (错误_文件_无效)如果企图创建一个零长度的文件映射

b)ERROR_INVALID_HANDLE(错误_无效_处理) 内存空间的命名和现有的内存映射,互斥量信号量临界区有同名

c)ERROR_ALREADY_EXISTS (错误或已经存在)表示内存空间命名已经存在

2)MapViewOfFile 

函数功能:将共享内存映射到进程的地址空间。(个人理解是将磁盘文件映射到内存中)

函数原型:

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

参数说明:

hFileMappingObject:文件映射对象的句柄。其值为CreateFileMapping和OpenFileMapping函数的返回值。

dwDesiredAccess:访问文件映射对象的类型(权限)。要与在CreateFileMapping()时设置的访问权限相匹配。

a)FILE_MAP_ALL_ACCESS等价于CreateFileMapping的FILE_MAP_WRITE | FILE_MAP_READ. 文件映射对象被创建时必须指定PAGE_READWRITE选项. 

b)FILE_MAP_COPY可以读取和写入文件.写入操作会导致系统为该页面创建一份副本.在调用CreateFileMapping时必须传入PAGE_WRITECOPY保护属性.

dwFileOffsetHigh和dwFileOffsetLow:文件映射起始偏移的高32位和低32位,用来告诉系统应该把数据文件中的哪个字节映射到视图中的第一个字节,通常情况都设置为0。

dwNumberOfBytesToMap:指定需要映射的文件的字节数量,当该值为0,则映射的是整个文件 。

函数返回值:

如果函数成功,则返回值是映射视图的起始地址。

如果函数失败,则返回值为NULL。 要获取扩展错误信息,请调用GetLastError。

3)OpenFileMapping

函数功能:打开共享内存,用于打开一个现成的文件映射对象。

函数原型:

HANDLE WINAPI OpenFileMapping(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName

参数说明:

dwDesiredAccess:访问文件映射对象的类型(权限)。要与在CreateFileMapping()时设置的访问权限相匹配。

a)FILE_MAP_ALL_ACCESS等价于CreateFileMapping的FILE_MAP_WRITE | FILE_MAP_READ. 文件映射对象被创建时必须指定PAGE_READWRITE选项. 

b)FILE_MAP_COPY可以读取和写入文件.写入操作会导致系统为该页面创建一份副本.在调用CreateFileMapping时必须传入PAGE_WRITECOPY保护属性.

bInheritHandle:如果此参数为TRUE,CreateProcess函数创建的进程可以继承该句柄; 否则,句柄不能被继承,一般情况下我们设置为FALSE。

lpName:要打开的文件映射对象的名称,也就是CreateFileMapping时的lpName。 

函数返回值:

如果函数成功,则返回值是指定文件映射对象的打开句柄。

如果函数失败,则返回值为NULL。 要获取扩展错误信息,请调用GetLastError。

4)UnmapViewOfFile

函数功能:从调用进程的地址空间取消映射文件的映射视图。用于释放内存区域。

函数原型:

BOOL UnmapViewOfFile( 
  LPCVOID lpBaseAddress 

参数说明:

lpBaseAddress:此值必须与上一次调用MapViewOfFile函数返回的值相同,其指向要取消映射的文件的映射视图的基址的指针。

函数返回值:

成功返回非零值,失败返回零,可调用GetLastError查看错误信息。

3. 代码示例

该代码示例涉及3个进程共同读写一块共享内存,因此要结合上一篇说的互斥量实现进程通信。进程A等待读信息,进程B和进程C写信息,在进程A读取进程B发送的信息后,进程C往共享内存写内容供进程A再次读取。因此进程A在读取信息后要将共享内存内容清零,方便下次读取。

实现流程:

·创建共享内存的进程

Step1:调用 CreateFileMapping 创建命名的内存映射文件对象时,Windows 即在物理内存申请一块指定大小的内存区域,返回文件映射对象的句柄 hMap。只要创建共享内存的进程没有关闭句柄hMap,以后运行的程序就会读出共享内存里面的数据

Step2:为了能够访问这块内存区域必须调用 MapViewOfFile 函数,促使 Windows 将此内存空间映射到进程的地址空间中。

·访问共享内存的进程

当在其他进程访问这块内存区域时,则必须使用OpenFileMapping 函数取得对象句柄 hMap,并调用 MapViewOfFile 函数得到此内存空间的一个映射。

这样一来,系统就把同一块内存区域映射到了不同进程的地址空间中,从而达到共享内存的目的。

#include "stdafx.h"
#include "SharedMemory.h"
#include "Mutex.h"
#include <iostream>
using namespace std;
const CString g_strMutexName  = L"MutexName_test";
const CString g_strMutexName1 = L"MutexName_test1";
const CString g_strSMName     = L"SharedMemoryName_test";
int _tmain(int argc, _TCHAR* argv[])
	CMutex			mutex(g_strMutexName);
	CMutex			mutex1(g_strMutexName1);
	CSharedMemory	sharedMemory(g_strSMName);
	if (!sharedMemory.Create())
		return -1;
	char chTest[1024];
	sharedMemory.Map();
	// 第一个进程发的信息
	cout<<"准备进入共享区域...\n";
	mutex.Lock();
	cout<<"准备读...\n";
	// 不加这个会出现这样的情况:一时能将共享内存内容读出来,一时读不出来
	// 猜测原因是写进去也要时间?
	Sleep(10);
	sharedMemory.Read(chTest, sizeof(chTest));
	cout<<"读到的结果为:";
	printf(chTest);
	sharedMemory.Clear(sizeof(chTest));
	mutex.UnLock();
	mutex.Close();
	// 第二个进程发的信息
	cout<<"\n准备进入共享区域...\n";
	mutex1.Lock();
	int k;
	cout<<"准备读...\n";
	sharedMemory.Read(&k, sizeof(k));
	cout<<"读到的结果为:"<<k;
	mutex1.UnLock();
	mutex1.Close();
	sharedMemory.Unmap();
	sharedMemory.Close();
	system("pause");
	return 0;
#include "stdafx.h"
#include "Mutex.h"
#include "SharedMemory.h"
#include <iostream>
using namespace std;
const CString g_strMutexName = L"MutexName_test";
const CString g_strSMName	 = L"SharedMemoryName_test";
int _tmain(int argc, _TCHAR* argv[])
	CMutex			mutex(g_strMutexName);
	CSharedMemory	sharedMemory(g_strSMName);
	if (!mutex.Create())
		return -1;
	sharedMemory.Open();
	sharedMemory.Map();
	cout<<"正在写...\n";
	char chSrc[]="this is test";
	int i = sizeof(chSrc);
	cout<<i;
	sharedMemory.Write(chSrc, sizeof(chSrc));
	cout<<"已写完...\n";
	mutex.UnLock();
	sharedMemory.Unmap();
	return 0;
#include "stdafx.h"
#include <iostream>
#include "Mutex.h"
#include "SharedMemory.h"
using namespace std;
const CString g_strMutexName1 = L"MutexName_test1";
const CString g_strSMName	 = L"SharedMemoryName_test";
int _tmain(int argc, _TCHAR* argv[])
	CMutex			mutex(g_strMutexName1);
	CSharedMemory	sharedMemory(g_strSMName);
	sharedMemory.Open();
	sharedMemory.Map();
	int k=1000;
	mutex.Create();
	sharedMemory.Write(&k, sizeof(k));
	sharedMemory.Unmap();
	mutex.UnLock();
	return 0;

CMutex类:

 CSharedMemory类:

这段话纯属个人牢骚,可忽略~写这几个exe是为了巩固多个进程通过共享内存进行通信理解,因为该内存是多个进程共同享有的,所以共享内存要配合互斥量的使用。于是先写了一篇文章关于互斥量的使用,在理解完互斥量后就开始着笔写这篇文章,感想1:先理解共享内存要用到的几个函数的作用,参数后再开始写代码简单多了。感想2:将互斥量和共享内存的使用封装成类方便多个工程使用,感受到了c++面向对象的魅力~感想3:写博客总结真的可以加深理解,即使这种理解是暂时记忆,但是后期自己遇到同样问题再查看自己写的相关博客可以唤醒记忆!即使没有唤醒相关记忆,由于自己写的博客是按自己容易接受学习的方式书写的,所以再次阅读重新学习时也会比较快入手!٩(๑❛ᴗ❛๑)۶

若该博客让你有收获,可以点个赞哟(❁´ω`❁)

以上是本博客的所有内容,若有改进之处,欢迎指正~ヾ(❀^ω^)ノ゙

共享内存的方式原理就是将一份物理内存映射到不同进程各自的虚拟地址空间上,这样每个进程都可以读取同一份数据,从而实现进程通信。因为是通过内存操作实现通信,因此是一种最高效的数据交换方法。 共享内存Windows 中是用 FileMapping 实现的,从具体的实现方法上看主要通过以下几步来实现: 1、调用 CreateFileMapping 创建一个内存文件映射对象; HANDLE C... 匿名管道(Anonymous Pipe)是 在父进程和子进程之间,或同一父进程的两个子进程之间传输数据的无名字的单向管道。邮件槽与命名管道相似,不过它传输数据是通过不可靠的数据报(如TCP/IP协议中的UDP包)完成的,一旦网络发生错误则无法保证消息正确地接收,而命名管道传输数据则是建立在可靠连接基础上的。不同于匿名管道的是命名管道可以在不相关的进程之间和不同计算机之间使用,服务器建立命名管道时给它指定一个名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信共享内存是一种进程间的通信机制(其他的通信机制还有管道,消息队列等)。进程之间通过访问一块共享的空间,来进行数据的通信(交换)。具体来讲,就是将一份物理内存映射到不同进程各自的虚拟地址空间,这样每个进程都可以读写这片物理内存。共享内存是速度最快的一种进程间通信(IPC)方式,它直接对内存进行存取,比操作系统提供的读写系统服务更快。由上面的描述我们发现,当多个进程对同一片空间进行读写时必然会出现同步的问题,所以一般共享内存会和信号量或者锁机制一同使用,保证数据的完整性。 1.Windows下的进程间共享内存是如何实现的? 2.对于读写进程,物理内存是什么时候分配的? 3.读写进程之间的同步和互斥共享内存机制中已经存在了,还是需要用户自己来实现? 4.利用“虚拟内存的检测”程序检测读进程所映射的虚拟地址空间块的信息。 进程通常被定义为一个正在运行的程序的实例,它由两个部分组成:        一个是操作系统用来管理进程的内核对象。内核对象也是系统用来存放关于进程的统计信息的地方       另一个是地址空间,它包含所有的可执行模块或DLL模块的代码和数据。它还包含动态分配的空间。如线程堆栈和堆分配空间。每个进程被赋予它自己的虚拟地址空间,当进程中的一个线程正在运行时,该线程可以访问只属于它的进程的内存。属于 (a)通过kernel命令行参数预留一些内存 这种方法,适合于需要大块的物理连续的内存。 假设物理内存总量为256M。命令行参数中,指定 mem=224M。即只让内核使用前224M内存,忽略其余的内存。 这样,我们就有了32M的内存可用,内存起始物理地址为224*1024*1024。 在内核态,通过ioremap,就可以将此物理地址处 1.共享内存介绍 共享内存指 (shared memory)在多处理器的计算机系统中,可以被不同中央处理器(CPU)访问的大容量内存。由于多个CPU需要快速访问存储器,这样就要对存储器进行缓存(Cache)。任何一个缓存的数据被更新后,由于其他处理器也可能要存取,共享内存就需要立即更新,否则不同的处理器可能用到不同的数据。共享内存是 Unix下的多进程之间的通信方法 ,这种方法通常用于一个程序的... 共享内存主要是通过映射机制实现的。   Windows 下进程的地址空间在逻辑上是相互隔离的,但在物理上却是重叠的。所谓的重叠是指同一块内存区域可能被多个进程同时使用。系统把同一块内存区域映射到了不同进程的地址空间中,从而达到共享内存的目的。   示例代码中,一方是QT编写的程序来创建并读取共享内存的内容,另一方是VC程序修改共享内存中的内容: #include "mainwindow.h" #include "ui_mainwindow.h" #include <QDebug> 一、共享内存原理 在32位的系统中,每一个进程都有4G连续的虚拟内存,且每一个进程这4G的虚拟内存块是互不共享。而为达到每个进程能够操作同一块内存,Window提供了内存映射文件的方式,简单的说每一个进程的一段虚拟内存对应于同一个文件或类文件的资源,使得每个进程能够操作同一个文件会类文件资源,从而达到内存共享的效果。 (1)为什么是4G? 32位系统寻址 2^32 = 4G 现代操作系统,对于内存管理,采⽤的是虚拟内存技术,也就是每个进程都有⾃⼰独⽴的虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存中。所以,即使进程A中和进程 B中的虚拟地址是⼀样的,其实访问的是不同的物理内存地址,对于数据的增删查改互不影响。就是拿出⼀块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写⼊的东⻄,另外⼀个进程⻢上就能看到了,都不需要拷⻉来拷⻉去,传来传去,⼤⼤提⾼了进程间通信的速度。(1)共享内存是双向通信(全双工)。(2)共享内存是IPC通信方式中速度最快的。 0、共享内存的方式原理就是将一份物理内存映射到不同进程各自的虚拟地址空间上,这样每个进程都可以读取同一份数据,从而实现进程通信。因为是通过内存操作实现通信,因此是一种最高效的数据交换方法。 调用 CreateFileMapping 创建一个内存文件映射对象: HANDLE CreateFileMapping( HANDLE hFile, // handle t 一、共享内存 1、什么是共享内存共享内存,就是多个进程共用一块逻辑内存,每个进程均可对该内存进行读写,是实现进程间通信(本地通信)的一种方式。在Windows和linux平台均可实现,实现流程相似,但调用的函数有所不同,这里只记录Windows平台下的实现方法。 2、实现流程 既然名为共享内存,在使用前肯定要先向系统申请内存。以两个进...