WinCE5.0中VirtualAlloc内存分配的试验代码

简介:

一、引

今天打算规划一下播放器的内存管理,初步设想先做一个SingleBuffer,然后在用一个BufferPool来管理这堆SingleBuffer,于是动手开始画UML类图,确定属性和行为。但是遇到了一个问题,就用malloc和free两个C语言函数构造整个内存管理吗?不甘心,这太弱了,必须有功能强劲一些的WINCE API来支持,尤其是每个内存块的起始地址对齐,好用点至少得32位对齐吧,酷一点得支持按PAGE_SIZE对齐吧。于是翻啊翻,抓到个壮丁,VirtualAlloc, 貌似功能强大,再搜一下, TCPMP和RealSDK也用它, 用葛优的话说就是"VirtualAlloc, 全国XXX用户, 我相信群众". 最后顺带搜出这篇文章,实在是经典中的经典:
《Windows CE.NET 高级内存管理》 http://www.microsoft.com/china/MSDN/library/windev/WindowsCE/WCEdncenetadvmemmgmt.mspx?mfr=true  
似乎以前看过,或者当时看的那篇只是“内存管理”而没有“高级”二字,或者就是当时太菜了没理解透,现在仔细读了几遍,拜啊拜,高人所写。

WINCE HELP中对VirtualAlloc的说明里,对第一个参数pAddress描述里写到“If the memory is being reserved, the specified address is rounded down to the next 64-KB boundary. If the memory is reserved and is being committed, the address is rounded down to the next page boundary. ” 如果不是上面提到这篇文章,我根本就不会注意到这句,因为TCPMP里的用法是:
p = VirtualAlloc(NULL,n,MEM_COMMIT,PAGE_READWRITE);
这第一个参数根本就忽略过去了。

下面的代码试验是基于这篇文章的。其中我最担心的一点,就是文章是针对WinCE.net的,而目前我用的版本是5.0,文中所提到的一些限制,是否已经有所改进了呢。

二、测试64KB对齐的直接分配方式

写个测试代码验证一下

#include "stdafx.h"
#include "Winbase.h"
#include "stdlib.h"

int WINAPI WinMain(     HINSTANCE hInstance,
            HINSTANCE hPrevInstance,
            LPTSTR    lpCmdLine,
             int       nCmdShow)
{
      //  TODO: Place code here.
     int i = 0;
     void* pMem = NULL;
    SYSTEM_INFO SystemInfo;

    GetSystemInfo(&SystemInfo);

    printf("SystemInfo.dwPageSize = %d \n", SystemInfo.dwPageSize);
    printf("SystemInfo.lpMinimumApplicationAddress = 0x%x \n", ( int)SystemInfo.lpMinimumApplicationAddress);
    printf("SystemInfo.lpMaximumApplicationAddress = 0x%x \n", ( int)SystemInfo.lpMaximumApplicationAddress);

    GlobalMemoryStatus(&MemStatus);
    printf("MemStatus.dwTotalPhys = %d \n", MemStatus.dwTotalPhys);
    printf("MemStatus. dwAvailPhys = %d \n", MemStatus.dwAvailPhys);
    printf("MemStatus.dwTotalVirtual = %d \n", MemStatus.dwTotalVirtual);
    printf("MemStatus.dwAvailVirtual = %d \n", MemStatus.dwAvailVirtual);

     while(1)
    {
        pMem = VirtualAlloc (0, 512, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

         if(!pMem)
        {
            printf("alloc %d times, failed, end",i+1);
             break;
        }
         else
        {
            printf("0x%X ",( int)pMem);
        }

        i++;
    }

    GlobalMemoryStatus(&MemStatus);
    printf("MemStatus.dwTotalPhys = %d \n", MemStatus.dwTotalPhys);
    printf("MemStatus. dwAvailPhys = %d \n", MemStatus.dwAvailPhys);
    printf("MemStatus.dwTotalVirtual = %d \n", MemStatus.dwTotalVirtual);
    printf("MemStatus.dwAvailVirtual = %d \n", MemStatus.dwAvailVirtual);

     return 1;
}


运行结果:

SystemInfo.dwPageSize = 4096
SystemInfo.lpMinimumApplicationAddress = 0x10000
SystemInfo.lpMaximumApplicationAddress = 0x7fffffff
MemStatus.dwTotalPhys = 55,656,448
MemStatus.dwAvailPhys = 40,923,136
MemStatus.dwTotalVirtual = 33,554,432
MemStatus.dwAvailVirtual = 31,850,496
0x60000  0x70000  0x80000  0x90000 
0xA0000  0xB0000  0xC0000  0xD0000
......
0x1E40000 0x1E50000 0x1E60000 0x1E70000
0x1E80000 0x1E90000 0x1EA0000 0x1EB0000
alloc 487 times to failed, END
MemStatus.dwTotalPhys = 55,656,448
MemStatus.dwAvailPhys = 38,903,808
MemStatus.dwTotalVirtual = 33,554,432
MemStatus.dwAvailVirtual = 0

很好,和高级内存管理一文描述的情况一样, 不过还是有些意外的发现. 我们再来仔细看一下 
(1) 所谓的next 64-KB boundary,换算成16进制也就是0x10000, 上面测试代码所得到的结果,的确是每次分配按0x10000递增的
(2) 的确在分配不足512次后,内存就用光了,分配失败停止了。512次计算为: (32M的进程虚拟地址空间 - 程序运行的一些开销) / 64K
(3) 计算一下刚才一共消耗了多少虚拟内存, 0x1EB0000 - 0x60000 = 0x1E50000 = 31,784,960 Byte,正好接近于32M.
那么实际上拿来使用的呢? ( 0x1EB0000 - 0x60000 ) / 0x10000 * 512 = 248,320 Byte
有没有搞错,我为了得到248K的内存来使用,却把32M虚拟内存用个精光
(4) 意外发现, 上面代码的物理内存的消耗量 = 40,923,136 - 38,903,808 = 2,019,328 并没有想象中的那么夸张真的用掉32M物理内存. 但它也不是512*487=249,344 Byte, 好玩乱试了一下,结果发现 2,019,328 / 4029 = 493, 正好整除, 并且略大于487, 那么可以理解为, (a) 物理内存的使用也是按PAGE_SIZE来消耗的,所以使用量必然为PAGE_SIZE的整数倍
(b) 虽然32M虚拟内存被消耗光了, 但实际上物理内存只消耗掉了VirtualAlloc Times * PAGE_SIZE, 因为我每次只要了512byte, 推测如果我每次要5000byte的话,那么物理内存消耗量就是VirtualAlloc Times * PAGE_SIZE * 2了.

TCPMP里似乎是犯了个错误,我在整个代码里搜索了一遍,的确是在所有调用VirtualAlloc的地方,第一个参数都是NULL或0。也就是说,TCPMP依靠其良好的内存回收管理机制,只用了不多次VirtualAlloc就得到了足够运行的内存,掩盖掉了在函数使用上的这个错误。比如MP3播放中,BUUFER CHAIN里每个内存是4096 byte,却需要使用一次VirtualAlloc用掉64K虚拟地址空间,虚拟内存的占用和使用比只有6.25%

所以WINCE应用程序中,要用VirtualAlloc进行内存分配正确方式,应该是按照“高级内存管理”一文中所说的方法,首先保留一块内存区域
pBase = VirtualAlloc (0, ReserveSize, MEM_RESERVE, PAGE_READWRITE);
注意第三个参数,只做MEM_RESERVE,而不直接MEM_COMMIT。按照文中所说,保留下来的内存区域,并不直接消耗掉物理内存。


三、测试只保留不提交的区域是否真正消耗内存

咱们来试一下

int WINAPI WinMain(    HINSTANCE hInstance,
            HINSTANCE hPrevInstance,
            LPTSTR    lpCmdLine,
             int       nCmdShow)
{
     void* pMem = NULL;
    MEMORYSTATUS MemStatus;

    GlobalMemoryStatus(&MemStatus);
    printf("MemStatus.dwTotalPhys = %d \n", MemStatus.dwTotalPhys);
    printf("MemStatus. dwAvailPhys = %d \n", MemStatus.dwAvailPhys);
    printf("MemStatus.dwTotalVirtual = %d \n", MemStatus.dwTotalVirtual);
    printf("MemStatus.dwAvailVirtual = %d \n\n", MemStatus.dwAvailVirtual);

    pMem = VirtualAlloc(0, 128*4096, MEM_RESERVE, PAGE_READWRITE);

    GlobalMemoryStatus(&MemStatus);
    printf("MemStatus.dwTotalPhys = %d \n", MemStatus.dwTotalPhys);
    printf("MemStatus. dwAvailPhys = %d \n", MemStatus.dwAvailPhys);
    printf("MemStatus.dwTotalVirtual = %d \n", MemStatus.dwTotalVirtual);
    printf("MemStatus.dwAvailVirtual = %d \n", MemStatus.dwAvailVirtual);
}

运行结果:
MemStatus.dwTotalPhys = 55,656,448
MemStatus.dwAvailPhys = 40,931,328
MemStatus.dwTotalVirtual = 33,554,432
MemStatus.dwAvailVirtual = 31,850,496

MemStatus.dwTotalPhys = 55,656,448
MemStatus.dwAvailPhys = 40,931,328
MemStatus.dwTotalVirtual = 33,554,432
MemStatus.dwAvailVirtual = 31,850,496
两次完全一样,对物理内存和虚拟内存都没有任何消耗。

无聊把上段代码的改成 MEM_RESERVE | MEM_COMMIT, 好奇看下运行结果
MemStatus.dwTotalPhys = 55,656,448
MemStatus.dwAvailPhys = 40,931,328
MemStatus.dwTotalVirtual = 33,554,432
MemStatus.dwAvailVirtual = 31,850,496

MemStatus.dwTotalPhys = 55,656,448
MemStatus.dwAvailPhys = 40,407,040
MemStatus.dwTotalVirtual = 33,554,432
MemStatus.dwAvailVirtual = 31,326,208
虚拟内存和物理内存消耗都正好是128*4096字节.


四、测试从保留内存从提交所需内存,按PAGE_SIZE自动对齐

最后,准备做内存块起始对齐了, 试验一下所谓的"If the memory is reserved and is being committed, the address is rounded down to the next page boundary"这个按PAGE_SIZE对齐是怎么回事

int WINAPI WinMain(    HINSTANCE hInstance,
            HINSTANCE hPrevInstance,
            LPTSTR    lpCmdLine,
             int       nCmdShow)
{
     int i = 0;
     void* pMem = NULL;

    pMem = VirtualAlloc(0, 10*4096, MEM_RESERVE, PAGE_READWRITE);

     while(1)
    {
        printf("pAddress = 0x%X \t", ( int)pMem + 888*i);

        pMem = VirtualAlloc ( ( void*)(( char*)pMem + 888*i), 888, MEM_COMMIT, PAGE_READWRITE);

         if(!pMem)
        {
            printf("alloc %d times, failed, end",i+1);
             break;
        }
         else
        {
            printf("pActual = 0x%X ",( int)pMem);
        }

        i++;
    }
}

恶搞一下,每次分配888字节内存.
结果是比较让人惊讶的
pAddress = 0x60000    pActual = 0x60000
pAddress = 0x60378    pActual = 0x60000
pAddress = 0x606F0    pActual = 0x60000
pAddress = 0x60A68    pActual = 0x60000
pAddress = 0x60DE0    pActual = 0x60000
pAddress = 0x61158    pActual = 0x61000
pAddress = 0x624D0    pActual = 0x62000
pAddress = 0x63848    pActual = 0x63000
pAddress = 0x64BC0    pActual = 0x64000
pAddress = 0x65F38    pActual = 0x65000
pAddress = 0x672B0    pActual = 0x67000
pAddress = 0x69628    pActual = 0x69000
pAddress = 0x6B9A0
alloc 13 times, failed, end
其中0x60000被重复分配了五次,如果是在实际应用中,这块数据恐怕被覆盖得骨头渣子也找不到了. 但仔细看看这句话,If the memory is reserved and is being committed, the address is rounded down to the next page boundary, 确实,谁都没错.比如在0x60378地址上,这个地址被保留了而未提交, 那么就不会轮到下一个PAGE, 而是当前PAGE的起始地址. 
所以, 很重要的一点就是,在从保留区域用MEM_COMMIT的方法取得实际内存时, Virtual第一个参数长度必须是PAGE_SIZE的整数倍, 否则很可能就出现内存重叠覆盖的情况了. 

关于VirtualAlloc的基本使用和测试代码到这边就可以结束了, 有发现什么新的关键点再继续写好了


本文转自Walzer博客园博客,原文链接:http://www.cnblogs.com/walzer/archive/2007/01/22/626562.html,如需转载请自行联系原作者


相关文章
|
3月前
|
存储 数据可视化 C++
提高代码效率的6个Python内存优化技巧
当项目变得越来越大时,有效地管理计算资源是一个不可避免的需求。Python与C或c++等低级语言相比,似乎不够节省内存。 但是其实有许多方法可以显著优化Python程序的内存使用,这些方法可能在实际应用中并没有人注意,所以本文将重点介绍Python的内置机制,掌握它们将大大提高Python编程技能。
95 0
|
1月前
|
IDE Linux 开发工具
内存泄漏检测工具Valgrind:C++代码问题检测的利器(一)
内存泄漏检测工具Valgrind:C++代码问题检测的利器
77 0
|
1月前
|
缓存 测试技术 开发工具
内存泄漏检测工具Valgrind:C++代码问题检测的利器(二)
内存泄漏检测工具Valgrind:C++代码问题检测的利器
35 0
|
3月前
小技巧分享:如何使用动态断点快速找到成对的 ABAP 内存 IMPORT 和 EXPORT 的代码位置
小技巧分享:如何使用动态断点快速找到成对的 ABAP 内存 IMPORT 和 EXPORT 的代码位置
23 0
|
3月前
|
Java 数据库连接
警惕内存泄漏与溢出!你的代码是否隐藏着致命危机?
警惕内存泄漏与溢出!你的代码是否隐藏着致命危机?
|
9月前
|
程序员 编译器 C语言
C语言指针理解 --- 代码配合图形讲解内存四区
C语言指针理解 --- 代码配合图形讲解内存四区
48 0
|
4月前
|
C++
c cpp 代码 数据竞争 分析, 以及 内存泄露 分析 工具 使用 demo
c cpp 代码 数据竞争 分析, 以及 内存泄露 分析 工具 使用 demo
30 0
|
5月前
|
存储 缓存 算法
深入理解内存管理:优化你的C++代码
深入理解内存管理:优化你的C++代码
|
5月前
|
监控 Java
电脑内存阈值监控代码示例
监控计算机内存的阈值有时候很重要。在Java中,可以使用Java Management Extensions (JMX) 来监控计算机内存的阈值。
466 0
|
10月前
|
存储 设计模式 缓存
解密Java享元模式:如何优化你的代码并减少内存占用?
解密Java享元模式:如何优化你的代码并减少内存占用?
1058 0

热门文章

最新文章