内存的分配管理也是操作系统的一个重要功能。里面有物理内存管理、虚存管理、应用程序内存管理,还有运行工作集、缓冲区的管理,也是内存管理的延续。我们这里以应用程序的内存管理为例,实现一个具有 Malloc 和 Free 功能的 API。由于内存的分配释放肯定要和物理地址打交道,所以我们需要将地址保存到数据表中,MemoryAlloc 表的 BaseAddress 字段存放的就是真实地址(转换为64位整数)。
这个例子使用的是伙伴式内存分配,使用首先匹配算法。
开发运行环境:.Net Framework 4.5,VS2013,SQL Server 2008R2,Entity Framework 5.0, 必须是64位。
1)内存分配伙伴算法流程:

2)内存释放伙伴算法流程:

1 程序运行
在开发阶段,还需要做以下事情:
- 建立数据表 MemoryAlloc 结构见下:
| Name | Type | Size | Default | AllowNull | PK | 备注 |
| BaseAddress | bigint | false | True | 内存基址 | ||
| Status | nvarchar(1) | 1 | False | 状态(A:Allocate, F:Free) | ||
| Length | bigint | 0 | false | 长度 |
2)使用Entity framework 5.0 的EDM框架,通过数据库生成实体及容器。
3)编译代码,执行 MemoryManager.exe。运行结果见后面的截图。
2 关键代码
//内存管理全部代码(MemoryMan.cs):
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
namespace MemoryManager
{
/// <summary>
/// 内存管理对象
/// </summary>
public unsafe class MemoryMan
{
/// <summary>
/// 公开构造
/// </summary>
public MemoryMan()
{
Ent = new MemoryDBEntities();
Buffer = IntPtr.Zero;
}
/// <summary>
/// Entity 容器
/// </summary>
public virtual MemoryDBEntities Ent { get; internal protected set;}
public static string Alloced = "A"; // 已经申请
public static string Freed = "F"; // 空闲
/// <summary>
/// 内存缓冲区
/// </summary>
public IntPtr Buffer { get; set;}
/// <summary>
/// 是否初始化
/// </summary>
public bool IsInit { get; set; }
/// <summary>
/// 初始化内存管理器
/// </summary>
/// <param name="initSize">全部管理内存的大小</param>
public void Init(int initSize)
{
if (IsInit)
return;
try
{
Buffer = Marshal.AllocHGlobal(initSize);
ClearData();
MemoryAlloc ma = new MemoryAlloc();
ma.BaseAddress = Buffer.ToInt64();
ma.Status = Freed;
ma.Length = initSize;
Ent.Set<MemoryAlloc>().Add(ma);
Ent.SaveChanges();
IsInit = true;
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 清空数据库数据,内部方法
/// </summary>
private void ClearData()
{
Ent.Database.ExecuteSqlCommand("delete from MemoryAlloc");
}
/// <summary>
/// 关闭
/// </summary>
public void Close()
{
if (!IsInit)
return;
try
{
if (Buffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(Buffer);
Buffer = IntPtr.Zero;
}
ClearData();
IsInit = false;
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 申请内存,无或失败返回 IntPtr.Zero
/// </summary>
/// <remarks>
/// 使用方法: byte* pbytes = (byte*)Malloc(1024)
/// </remarks>
/// <param name="size">要申请的大小</param>
/// <returns>地址</returns>
public IntPtr Malloc(int size)
{
if (size == 0)
return IntPtr.Zero;
var result = Ent.MemoryAlloc.Where(x => (x.Length >= size) && (x.Status == Freed)).
OrderBy( x => x.BaseAddress).FirstOrDefault();
if (result == null) // 未找到
return IntPtr.Zero;
IntPtr ret = new IntPtr(result.BaseAddress);
// 申请内存
result.Status = Alloced;
if (result.Length == size) // 大小合适,正好分配,无需其它操作
{
}
else // 偏大,分裂为两个,第一个分配,第二个空闲。
{
long oldlen = result.Length;
IntPtr newaddr = IntPtr.Add(ret, size);
result.Length = size;
MemoryAlloc ma = new MemoryAlloc();
ma.BaseAddress = newaddr.ToInt64();
ma.Status = Freed;
ma.Length = oldlen - size;
Ent.Set<MemoryAlloc>().Add(ma);
}
Ent.SaveChanges();
return ret;
}
/// <summary>
/// 释放内存,忽略其它无效情况
/// </summary>
/// <param name="freePtr">要释放的地址</param>
public void Free(IntPtr freePtr)
{
if (freePtr == IntPtr.Zero)
return;
var freebase = freePtr.ToInt64();
var me = Ent.MemoryAlloc.Where ( x => (x.BaseAddress == freebase) && (x.Status == "A")).FirstOrDefault();
if (me == null) // 未找到
return;
// 释放内存。
me.Status = Freed;
// 合并右边邻接空闲块。
var combinRight = Ent.MemoryAlloc.Where(x => ((x.Status == Freed) && (x.BaseAddress > freebase))).
OrderBy( x => x.BaseAddress).FirstOrDefault();
if (combinRight != null)
{
if ((me.BaseAddress + me.Length) == combinRight.BaseAddress)
{
me.Length += combinRight.Length;
Ent.MemoryAlloc.Remove(combinRight);
}
}
// 合并左边邻接空闲块。
var combinLeft = Ent.MemoryAlloc.Where(x => (x.Status == Freed) && (x.BaseAddress < freebase)).
OrderByDescending( x => x.BaseAddress).FirstOrDefault();
if (combinLeft != null)
{
if ((combinLeft.BaseAddress + combinLeft.Length) == me.BaseAddress)
{
combinLeft.Length += me.Length;
Ent.MemoryAlloc.Remove(me);
}
}
Ent.SaveChanges();
}
}
}
主窗体相关代码(MemoryMan.cs):
1>初始化:
/// <summary>
/// 初始化内存管理器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void buttonInit_Click(object sender, EventArgs e)
{
mm.Init(int.Parse(maskedTextBoxInit.Text));
LoadData();
buttonMalloc.Enabled = buttonFree.Enabled =
buttonFreeBySelected.Enabled = buttonMalloc10AndFill0.Enabled = mm.IsInit;
buttonInit.Enabled = !mm.IsInit;
}
2>申请 10 字节并填 0:
/// <summary>
/// 申请10字节内存并填 0
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void buttonMalloc10AndFill0_Click(object sender, EventArgs e)
{
int size = 10;
var qptr = mm.Malloc(size);
if (qptr == IntPtr.Zero)
return;
byte* pbytes = (byte*)qptr;
if (pbytes == null)
return;
var qptrend = (byte*)(qptr + size);
while (pbytes++ < qptrend)
*pbytes = 0;
LoadData();
}
3 执行结果
1)程序的入口界面:

2)点击Init 后:

3)点击3 次 Malloc By Sze 申请内存:
(原文未截图)
4)输入第一块内存地址然后点击Free By Address:

5)输入第三块内存地址然后点击Free By Address:

6)在网格选中第二块内存地址然后点击 Free By Grid Selected:

7)点击 Malloc 10 And Fill 0:

4 扩展阅读
我们上面的例子是有关首先分配算法的,其实我们也可以实现其它分配算法,只要修改Linq查询语句的排序列就行:
| 排序列 | 方向 | 实现的算法 |
| BaseAddress | 正向 | 首先匹配 |
| Length | 正向 | 最佳匹配 |
| BaseAddress | 反向 | 最后匹配 |