每日Bug:(2)共享内存

对于整个系统而言,主存与CPU的资源都是有限的,随着打开进程数量的增加,若是将所有进程运行所需的代码/数据/栈/共享库都存放在主存中,那么开启一部分进程就可以将主存占用完。

虚拟内存就是解决以上问题的方法,使用虚拟内存不用将进程全部内容加载到主存上(局部性立了大功------进程在某段时间只需要用到部分代码及其数据),虚拟内存与主存通过不断的数据交换,只需要占用满足程序运行最小空间的内存即可完成程序运行。

不光如此,虚拟内存为程序分配同样的空间便于管理(意味着进程自认为占用了整个内存)。同时,虚拟内存为每个进程增添地址空间的安全性,保护其地址空间不被其他进程所破坏。

正如上述所说虚拟内存的优点,共享内存基于虚拟内存通过内存映射的方式应运而生。

数据交互的方式有很多种且各有千秋,比如匿名管道、有名管道、信号、消息队列。为什么选择共享内存?同一台主机下多进程之间通信,其他方式需要通过内核进行四次数据交互才能完成一次通信,但共享内存只需要两次且不经过内核。

相对于其他方式,共享内存占用的CPU资源更少且速度更快。不幸的是,共享内存缺少对同步的控制,需要通过其他方式去控制对内存读取同步。弥补共享内存的不足,可以通过对写入数据时设置有效位,这样通过对位的检查就能有效控制数据同步。

原理:

在Windows下每个进程都存在一张页表,通过MMU将进程的虚拟地址映射到真实的物理内存。基于虚拟内存的使用原理,共享内存在此基础上通过函数指定方式将多个进程的虚拟内存同时指向同一片共享内存地址空间,并通过函数参数控制进程的访问权限等参数,从而完成多个进程间的数据通信。

Windows下共享内存API函数

句柄

句柄作为共享内存机制的重要概念,日常编程中不会经常用到。将句柄具象化可以理解为"刀柄",操作系统"手握"句柄指向一块区域,区域中存有各个对象的地址、属性等信息,操作系统通过"刀柄"可以顺藤摸瓜找到对象所在以及其他信息。

在windows编程中,有个概念是句柄,句柄指向资源(一切可以利用的物理的逻辑的资源),其中文件操作,可以将文件映射到内存,此处的文件是广义的文件,可以指内存对象,邮件槽等。

在windows中创建一个指向文件的虚拟内存,然后多个进程创建各个进程对这块内存的映射,通过访问各个进程的映射内存对这块虚拟内存进行访问,是共享内存实现的原理。

CreateFileMappingA函数

cpp 复制代码
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);

返回值:创建成功返回文件句柄,创建失败则返回NULL。

OpenFileMappingA函数

cpp 复制代码
HANDLE OpenFileMappingA(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName
);



OpenFileMappingA(FILE_MAP_ALL_ACCESS,FALSE,lpName);

**返回值:**成功返回文件句柄,失败则返回NULL。

MapViewOfFile函数

cpp 复制代码
LPVOID MapViewOfFile(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
DWORD dwNumberOfBytesToMap
);

MapViewOfFile(hmapfile,FILE_MAP_ALL_ACCESS,0,0,0);

**返回值:**成功返回映射视图起始地址,失败返回NULL。

UnmapViewOfFile函数

cpp 复制代码
BOOL UnmapViewOfFile(LPCVOID lpBaseAddress);

UnmapViewOfFile(lpbase);

返回值:成功返回非零,失败返回零。

CloseHandle函数

cpp 复制代码
BOOL CloseHandle(HANDLE hObject);

CloseHandle(hmap);

返回值:成功返回非零。

Handle

Handle的用途就是实现线程之间的通信,例如:当子线程做一个耗时的操作的时候,我们并不知道他什么时候做完,做完了也不知道,那么,我们是不是需要一个东西来通知我们,让我们知道呀,所以说,也就有了Handle的来源,没有Handle这个东西,对我们来说,非常的麻烦。句柄(Handle)是一个用于标识和管理系统资源的抽象标识符。不同类型的句柄用于管理不同类型的资源。

演示:

1、写入端:

cpp 复制代码
#include<iostream>
#include<windows.h>
using namespace std;

int main() {
	unsigned long buffsize = 100;
	// 创建或打开一个命名的内存映射文件对象
	HANDLE m_handle= CreateFileMapping(
		INVALID_HANDLE_VALUE, // 使用系统分页文件: INVALID_HANDLE_VALUE表示共享未与文件关联的内存
		NULL,  // 默认安全性,决定返回的句柄是否可以被子进程继承,如果为 NULL,则不能。
		PAGE_READWRITE,  // 可读写权限,当取 PAGE_READWRITE 时,表示对文件映射对象有可读可写的权限
		0,  // 最大对象大小(高位)
		buffsize, // 最大对象大小(低位)
		L"SharedMemory"
	);
	if (m_handle == NULL) {
		cout << "CreateFileMapping failed: " << GetLastError() << endl;
		return 1;
	}
	// 映射缓冲区视图,如果函数成功,则返回值是映射视图的起始地址。
	// 将文件映射对象映射进调用的进程的虚拟地址空间
	LPVOID pVoid = MapViewOfFile(
		m_handle, // 映射对象句柄
		FILE_MAP_ALL_ACCESS, // 可读写许可
		0,//表示文件映射起始偏移的高32位
		0,//表示文件映射起始偏移的低32位
		buffsize// 映射大小,文件中要映射的字节数。0表示映射整个文件映射对象,单位是字节。

	);
	if (pVoid == NULL) {
		cout << "MapViewOfFile failed" << GetLastError << endl;
		CloseHandle(m_handle);
		return 1;
	}
	HANDLE Semaphore_write = CreateSemaphore(NULL, 1, 1 ,L"MySemaphore");
	if (Semaphore_write == NULL) {
		cout << "CreateSemaphore failed: " << GetLastError() << endl;
		UnmapViewOfFile(pVoid);
		CloseHandle(m_handle);
		return 1;
	}
	int num=1;
	while (true) {
		WaitForSingleObject(Semaphore_write,INFINITE);
        memcpy(pVoid,&num, sizeof(num));
		FlushViewOfFile(pVoid, buffsize); // 确保写入生效
		cout << "write:num=" << num << endl;
		num++;
		ReleaseSemaphore(Semaphore_write, 1, NULL);
		Sleep(1000);
	}
	UnmapViewOfFile(pVoid); //取消文件映射后,进程中该部分的地址会被释放(可以理解成free操作),然后该部分的内存可以被用于其他分配。
	CloseHandle(Semaphore_write);
	CloseHandle(m_handle);
}

2、读取端:

cpp 复制代码
#include<iostream>
#include<windows.h>
using namespace std;

int main() {
	unsigned long buffsize = 100;
	HANDLE m_handle = OpenFileMapping(
		FILE_MAP_ALL_ACCESS,
		FALSE, // 继承标志
		L"SharedMemory"
	);
	if (m_handle == NULL) {
		cout << "CreateFileMapping failed: " << GetLastError() << endl;
		return 1;
	}
	LPVOID pVoid = MapViewOfFile(
		m_handle,
		FILE_MAP_ALL_ACCESS,
		0,
		0,
		buffsize
	);

	if (pVoid == NULL) {
		cout << "MapViewOfFile failed" << GetLastError << endl;
		CloseHandle(m_handle);
		return 1;
	}

	HANDLE Semaphore_read = OpenSemaphore(
		SYNCHRONIZE | SEMAPHORE_MODIFY_STATE, // 需要这两个权限
		FALSE,                                // 不继承
		L"MySemaphore"                     // 必须和写入程序一致
	);
	if (Semaphore_read == NULL) {
		cout << "OpenSemaphore failed: " << GetLastError() << endl;
		UnmapViewOfFile(pVoid);
		CloseHandle(m_handle);
		return 1;
	}
	int read_num = 0;
	while (true) {
		WaitForSingleObject(Semaphore_read, INFINITE);
		memcpy(&read_num, pVoid, sizeof(read_num));
		cout << "read num=" << read_num << endl;
		ReleaseSemaphore(Semaphore_read, 1, NULL);
		Sleep(1000);
	}
	UnmapViewOfFile(pVoid);
	CloseHandle(m_handle);
	CloseHandle(Semaphore_read);
	return 0;
}

3、运行结果:

相关推荐
华纳云IDC服务商26 分钟前
华纳云:centos如何实现JSP页面的动态加载
java·linux·centos
致微1 小时前
【Vue bug】:deep()失效
前端·vue.js·bug
云中飞鸿1 小时前
加载ko驱动模块:显示Arm版本问题解决!
linux·arm开发
二进制coder1 小时前
ARM32静态交叉编译并使用pidstat教程
linux
豪越大豪2 小时前
豪越科技消防立库方案:实现应急物资高效管理
大数据·运维
郑梓妍2 小时前
(持续更新)Ubuntu搭建LNMP(Linux + Nginx + MySQL + PHP)环境
linux·ubuntu·php
xflm3 小时前
qemu(3) -- qemu-arm使用
linux
xflm3 小时前
qemu(4) -- qemu-system-arm使用
linux
hlsd#3 小时前
轻松实现CI/CD: 用Go编写的命令行工具简化Jenkins构建
运维·ci/cd·go·jenkins
Tesseract_95274 小时前
【Linux】VSCode用法
linux·c语言·vscode