漫谈Windows共享内存
虚拟内存
在讲共享内存之前先来聊聊虚拟内存的那些事。
对于整个系统而言,主存与CPU的资源都是有限的,随着打开进程数量的增加,若是将所有进程运行所需的代码/数据/栈/共享库都存放在主存中,那么开启一部分进程就可以将主存占用完。
虚拟内存就是解决以上问题的方法,使用虚拟内存不用将进程全部内容加载到主存上(局部性立了大功——进程在某段时间只需要用到部分代码及其数据),虚拟内存与主存通过不断的数据交换,只需要占用满足程序运行最小空间的内存即可完成程序运行。
不光如此,虚拟内存为程序分配同样的空间便于管理(意味着进程自认为占用了整个内存)。同时,虚拟内存为每个进程增添地址空间的安全性,保护其地址空间不被其他进程所破坏。
正如上述所说虚拟内存的优点,共享内存基于虚拟内存通过内存映射的方式应运而生。
为什么选择共享内存
数据交互的方式有很多种且各有千秋,比如匿名管道、有名管道、信号、消息队列。为什么选择共享内存?同一台主机下多进程之间通信,其他方式需要通过内核进行四次数据交互才能完成一次通信,但共享内存只需要两次且不经过内核。
相对于其他方式,共享内存占用的CPU资源更少且速度更快。不幸的是,共享内存缺少对同步的控制,需要通过其他方式去控制对内存读取同步。
要弥补共享内存的不足,可以通过对写入数据时设置有效位,这样通过对位的检查就能有效控制数据同步。
共享内存原理
在Windows下每个进程都存在一张页表,通过MMU将进程的虚拟地址映射到真实的物理内存。基于虚拟内存的使用原理,共享内存在此基础上通过函数指定方式将多个进程的虚拟内存同时指向同一片共享内存地址空间,并通过函数参数控制进程的访问权限等参数,从而完成多个进程间的数据通信。
▲图1.1 共享内存原理图
Windows下共享内存API函数
4.1 函数头文件
实现共享内存使用的头文件,在Windows环境下使用头文件。
4.2 句柄
句柄作为共享内存机制的重要概念,日常编程中不会经常用到。将句柄具象化可以理解为“刀柄”,操作系统“手握”句柄指向一块区域,区域中存有各个对象的地址、属性等信息,操作系统通过“刀柄”可以顺藤摸瓜找到对象所在以及其他信息。
4.3 CreateFileMappingA函数
4.3.1 函数结构
HANDLE CreateFileMappingA( HANDLE hFile, LPSECURITY_ATTRIBUTES lpAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCTSTR lpName ) ; /* 示例 */ HANDLE hmap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_COMMIT, 0, FILESIZE, lpName);
4.3.2 参数说明
4.3.3 返回值
创建成功返回文件句柄,创建失败则返回NULL。
4.4 OpenFileMappingA函数
4.4.1 函数结构
HANDLE OpenFileMappingA( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName ) ; /* 示例 */ OpenFileMappingA( FILE_MAP_ALL_ACCESS, FALSE, lpName );
4.4.2 参数说明
4.4.3 返回值
成功返回文件句柄,失败则返回NULL。
4.5 MapViewOfFile函数
4.5.1 函数结构
LPVOID MapViewOfFile( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, DWORD dwNumberOfBytesToMap ) ; /* 示例 */ MapViewOfFile( hmapfile, FILE_MAP_ALL_ACCESS, 0, 0, 0 );
4.5.2 参数说明
4.5.3 返回值
成功返回映射视图起始地址,失败返回NULL。
4.6 UnmapViewOfFile函数
4.6.1 函数结构
BOOL UnmapViewOfFile( LPCVOID lpBaseAddress ) ; /* 示例 */ UnmapViewOfFile(lpbase);
4.6.2 参数说明
4.6.3 返回值
成功返回非零,失败返回零。
4.7 CloseHandle函数
4.7.1 函数结构
BOOL CloseHandle( HANDLE hObject ) ; /* 示例 */ CloseHandle(hmap);
4.7.2 参数说明
4.7.3 返回值
成功返回非零。
模拟共享内存
以下例子需要先打开server.exe可执行程序,再打开client.exe可执行程序。本例子中,通过互传的数组p[0]控制server方写(0),或者client方读(1)。
5.1 server.c
#include #include #include #define lpName "shareMemory" #define FILESIZE 1024 LPVOID lpdata = NULL; void main() { if (lpdata != NULL) { printf("ShareMemory exists.\n"); } HANDLE hmap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_COMMIT, 0, FILESIZE, lpName); if (hmap == NULL) { puts("CreateFileMappingA fail.\n"); system("pause"); } else { lpdata = MapViewOfFile(hmap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); if (lpdata == NULL) { printf("MapViewOfFile fail.\n"); system("pause"); } else { int *p = lpdata; while (1) { if (!p[0]) { int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; memcpy(lpdata, a, 40); } } } } system("pause"); UnmapViewOfFile(lpdata); CloseHandle(hmap); system("pause"); }
5.2 client.c
#include #include #include #define lpName "shareMemory" void main() { HANDLE hmapfile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, lpName); if (hmapfile == NULL) { printf("OpenFileMappingA fail.\n"); system("pause"); } else { LPVOID lpbase = MapViewOfFile(hmapfile, FILE_MAP_ALL_ACCESS, 0, 0, 0); if (lpbase == NULL) { printf("MapViewOfFile fail.\n"); system("pause"); }else { int *p = lpbase; while (1) { if (!p[0]) { continue; } else { for (int i = 1; i < 10; i++) { printf("%d\n", p[i]); } int a[1] = {0}; memcpy(lpbase, a, 4); } } } UnmapViewOfFile(lpbase); CloseHandle(hmapfile); system("pause"); } }
SkyEye天目全数字实时仿真软件
迪捷软件自主研发的SkyEye天目全数字实时仿真软件,是基于可视化建模的硬件行为级仿真平台,利用拖拽的方式快速搭建任意的虚拟硬件平台,保证虚拟嵌入式系统的可靠性和实时性,进行嵌入式软件的开发和调试。SkyEye目前支持主流的嵌入式硬件平台,可以运行主流的操作系统,此外还能适配国内自主研发的操作系统天脉。通过利用基于LLVM的动态二进制翻译技术,使虚拟处理器在典型的桌面计算机上运行速度可以达到2000MIPS以上。SkyEye支持调用Windows库函数,结合项目实际运行环境,当满足使用共享内存的条件时,通过共享内存完成软件与SkyEye工程之间的数据交互方式让降低项目成本、缩短项目周期成为可能。