Files
GdCpp12/include/Mem/CMem.h

371 lines
13 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#ifndef CMem_H
#define CMem_H
#include "GdCPP_Exports.h"
#include <stdint.h>
#include <mutex>
#include <map>
/**
* @addtogroup Memory
** @brief
** @details
* @{
*
*/
///
/// \brief 内存管理的基类,实现:<br>
///
/// 需要了解的基本概念:
/// - 物理内存:内存条 + 内存交换文件
/// - 虚拟内存应用程序认为它拥有连续的可用的内存win10 64位有128T
/// - 内存交换文件paging file: 内存不够用了,系统将最近没使用的内存保存到文件中,用到再交换回来。
/// - page size系统管理内存的单元x86、x64系统使用4-KB, IA-64用8-KB.
/// - 内存分配粒度windows从虚拟内存分配一个range时的最小单位win10是64k
/// - large page: 大内存的应用为优化内存访问以比page大的多的单位使用物理内存好像是2M.
/// 刚开机的时候分配没问题,电脑运行一段时间就没有足够连续的物理内存来分配了。
///
/// 需要了解的windows内存操作reserve/commit/decommit/release/lock/unlock
///
/// 参考资料:
/// - 《windows核心编程》第五版
/// - [windows memory management](https://docs.microsoft.com/en-us/windows/win32/memory/memory-management)<br>
/// - [windows memory api](https://docs.microsoft.com/en-us/windows/win32/api/memoryapi)<br>
/// </pre>
/// \brief CMem 定义了一块内存,包括逻辑、物理两个方面。
/// - 内存的逻辑属性:包括内存地址、可用内存大小、外部容器大小。
/// - 内存的物理方法:包括内存如何分配、释放、调整大小的函数接口。
/// CMem作为所有内存的基类物理层定义了一个在现有内存中取一部分使用。
class GDCPP_API CMem
{
public:
virtual ~CMem()
{
//dealloc(); // 基类析构没必要清空属性变量了
};
// ----------- 逻辑内存 -----------
/// \brief 内存起始地址。可以引用但不要直接更改通过alloc()、dealloc()来修改
uint8_t* Addr = 0;
/// \brief 可用内存大小
size_t memSize = 0;
/// 容器大小containerSize>=memSize
/// 不同子类有不同的容器。
/// - 基类容器就是已经分配好的一块内存
/// - CWinMem的容器是AllocVirtual函数分配的虚拟内存空间
size_t containerSize = 0;
bool selfAlloc = false; // 是否自己分配的内存,如果是,析构时需要释放
// ----------- 物理内存:分配、释放、调整大小 -----------
/// @brief 基类在指定容器中分配内存。子类需要重载。
/// @param addr 容器所在地址
/// @param container 容器大小
/// @param size 实际使用大小。
/// @return 是否分配成功
virtual bool alloc(void* addr, size_t container, size_t size) {
// 注意Addr当前不知道时可以赋0container不能为0
ASSERT(container != 0);
ASSERT(size <= container);
Addr = static_cast<uint8_t*>(addr);
containerSize = container;
memSize = size;
selfAlloc = false;
return true;
}
/// @brief 如果子类没有重载默认调用alloc(nullptr, container, size)
virtual bool alloc(size_t container, size_t size)
{
return alloc(nullptr, container, size);
}
/// @brief 如果子类没有重载默认调用alloc(addr, size, size)而不是alloc(addr, size, 0)
virtual bool alloc(void* addr, size_t size) {
return alloc(addr, size, size);
};
/// @brief 如果子类没有重载默认调用alloc(nullptr, size, size)而不是alloc(nullptr, size, 0)
virtual bool alloc(size_t size)
{
return alloc(nullptr, size, size);
}
/// \brief 基类不自动释放内存。子类需要重载实现释放内存。
virtual void dealloc() {
Addr = nullptr;
memSize = 0;
containerSize = 0; //如果不想containerSize清0使用resize()函数
selfAlloc = false;
}
// 下面两个函数调整内存大小区别在于如果原先的内存有多的一个释放掉一个不释放只减小memSize。
/// 在容器范围内调整可用内存的大小,多余的释放。
/// 如果containerSize不够时基类只返回false
/// 子类需要重载决定是重新申请还是返回false。
virtual bool resize(size_t size) {
if (containerSize == 0) //不知道容器大小
return false;
if (size <= containerSize) {
memSize = size;
return true;
} else {
return false;
}
};
/// 在容器范围内调整可用内存的大小多余的不释放只减小memSize不释放。
/// 如果containerSize不够时基类只返回false
/// 子类需要重载决定是重新申请还是返回false。
virtual bool reserve(size_t size) {
if (containerSize == 0) //不知道容器大小
return false;
if (size <= containerSize) {
memSize = size;
return true;
} else {
return false;
}
}
/// \brief 从操作系统的的虚拟空间分配内存,不分配物理内存
/// \param addr 分配到指定地址。如果不指定地址,请用后面的同名函数更方便,并增加确认内存之前是空的。
/// \param size 空间大小会保存到PhysicSize并设置freeFunc指针为freeInVirtual
/// \param commit 是否立即分配物理内存
virtual bool _allocVirtual(void* addr, size_t size, bool commit);
static uint8_t* allocVirtual(size_t size);
static void freeVirtual(uint8_t* addr);
protected:
/// 有些虚拟内存空间需要临时释放,再分拆、合并。
/// 为避时释放时被别的线程占了,采用以下两个措施:<br>
/// - 需要分拆的内存规定地址空间从后向前分配。<br>
/// - 临时释放前加锁,重新占用后解锁
static std::mutex uplocker;
/// \brief 从操作系统的的虚拟空间分配内存,不分配物理内存
/// \param addr 分配到指定地址。如果不指定地址,请用后面的同名函数更方便,并增加确认内存之前是空的。
/// \param size 空间大小会保存到PhysicSize并设置freeFunc指针为freeInVirtual
/// \param commit 是否立即分配物理内存
virtual bool _allocUpperVirtual(size_t size, bool commit);
};
/// 统计内存使用的接口类。需要统计内存使用情况的子类继承此类。
/// 内存统计功能没有合并到CMem的原因
/// 内存的分配、释放时必须功能,而内存统计是个辅助功能。
/// 基类是在现有内存中划一块使用,是不需要统计内存使用量,使其更轻量一点。
class GDCPP_API CMemUsage
: virtual public CMem // 虚继承CMem这样在统计表中访问CMemUsage指针就可以获取其Addr、containerSize和memSize
{
public:
CMemUsage()
{
// 构造时就自动添加到统计表,但未命名
std::lock_guard guard(MemListLock); // 索引表加锁
MemList.push_back(this);
}
virtual ~CMemUsage()
{
// 析构时从统计表中删除
std::lock_guard guard(MemListLock); // 加锁
for (auto it = MemList.begin(); it != MemList.end(); it++) {
if (*it == this) {
MemList.erase(it);
return ;
}
}
}
// 不允许复制构造
CMemUsage(const CMemUsage&) = delete;
/// @brief 被统计的内存块需要起一个名称。构造时为空,需要用户层指定名称,最好不要重名,可以是中文。
std::wstring memName;
/// @brief 内存类型名称,通常在子类构造时设定,方便分类统计。
std::wstring memType;
/// @brief 统计已经分配的内存ContainerSize总和调用updateUsage()、report()时更新最新值
static size_t totalContainerSize;
/// @brief 统计已经分配的内存memSize总和调用updateUsage()、report()时更新最新值
static size_t totalMemSize;
/// @brief 分配的同时命名
// virtual bool allocNamed(const wchar_t* name, size_t container, size_t size) = 0;
virtual bool allocNamed(const std::wstring& name, size_t container, size_t size) = 0;
protected:
/// @brief 所有已经内配内存索引表,用于统计所有分配的内存
static std::list<CMemUsage*> MemList;
/// @brief 索引表添加、删除时加锁
static std::mutex MemListLock;
/// @brief 更新统计值。内部调用,未加锁
static void _updateUsage()
{
totalContainerSize = 0;
totalMemSize = 0;
for (auto mem : MemList)
{
totalContainerSize += mem->containerSize;
totalMemSize += mem->memSize;
}
}
public:
/// @brief 更新统计值。外部调用,加锁
static void updateUsage()
{
std::lock_guard guard(MemListLock); // 加锁
_updateUsage();
}
/// @brief 输出报表到str
static void report(std::wstring& str);
virtual std::wstring details()
{
return std::wstring();
}
};
class GDCPP_API CWinMem
: virtual public CMemUsage // 继承内存统计接口
{
public:
CWinMem()
{
memType = L"Win"; // 标记内存内存类型
}
~CWinMem() override
{
dealloc();
}
using CMem::alloc; // 防止基类的同名函数被子类掩盖
/// @brief 分配虚拟内存
/// \param addr 可以为0由win自动分配一个对齐64k的地址可以为指定地址但必须是未分配的。
/// \param container 虚拟内存大小必须大于0。内部会对齐到64k
/// \param size 提交的物理内存大小。size <= container
/// 可以等于0之后再用reserve()、resize()提交物理内存。
/// \return 是否分配成功。
virtual bool alloc(void* addr, size_t container, size_t size) override;
/// @brief 分配虚拟内存成功后给它命名为name
virtual bool allocNamed(const std::wstring& name, size_t container, size_t size) override
{
const bool ret = alloc(nullptr, container, size);
if (ret) memName = name;
return ret;
}
/// @brief 特殊应用场景从虚拟内存的高端地址往下分配
bool allocUpper(size_t container, size_t size);
virtual bool resize(size_t size) override;
virtual bool reserve(size_t size) override;
virtual void dealloc() override;
std::wstring details() override;
};
/// 一段内存映射到一个内存映射文件,当访问超出文件顶部时,自动翻转到文件底部;超出底部时自动翻转到顶部。
///
/// <pre>
/// 如下图所示:
/// 内存 映像文件
/// -------------
/// | 2 |
/// ============== --- ==============
/// | 1 | | 1 |
/// -------------- --------------
/// | 2 | | 2 |
/// ============== --- ==============
/// | 1 |
/// --------------
/// </pre>
class GDCPP_API CFileMapMem2
: virtual public CMemUsage
{
public:
CFileMapMem2()
{
memType = L"FileMap"; // 标记内存内存类型
}
~CFileMapMem2()
{
dealloc();
}
using CMem::alloc; // 防止基类的同名函数被子类掩盖
/// @brief 执行了以下操作:
/// - 分配2*container大小的虚拟空间未分配物理内存。
/// - 创建了内存映射文件,但未映射到内存
/// @param name 名字保存到memName同时加了一个前缀用于创建内存映射文件
/// @param container 内存映射文件的大小。
/// @param size 本类此参数为0
/// @return
virtual bool allocNamed(const std::wstring& name, size_t container, size_t size) override final;
virtual void dealloc() override final;
/// @brief 调整大小必须在0和containerSize两个之间调整不支持其它数值。
virtual bool resize(size_t size) override final;
/// @brief 调整大小必须在0和containerSize两个之间调整不支持其它数值。
virtual bool reserve(size_t size) override final
{
return resize(size);
};
/// @brief 本类必须使用带名称的分配函数不带名字的直接返回false
bool alloc(void* addr, size_t container, size_t size) override final { return false; }
protected:
// 本类自己的Addr、containerSize、memSize保存供外部访问的地址空间
// 以下3个成员保存整个内存空间、顶部、底部只读访问的映射空间
CMem allMem; // 保存整个虚拟内存空间
CMem TopMem; // 地址空间在顶部,映射到文件的底部
CMem BottomMem; // 地址空间在底部,映射到文件的顶部
/// 内存镜像文件的句柄
HANDLE hand=nullptr;
/// 共享文件的名称
std::wstring _nativeKey;
/// 出错码
int _error;
/// 出错信息
std::wstring _errorString;
bool mapFile();
void unmapFile(bool realloc);
public:
/// @brief 验证内存映射是否正确
/// @return
bool verifyMap(bool compare_content=false);
std::wstring details() override;
};
/**
* @} //group
*/
#endif // CMem_H