wince内存分配及管理

来源:互联网 发布:网络分层 编辑:程序博客网 时间:2024/05/18 02:29

一、引

今天打算规划一下播放器的内存管理,初步设想先做一个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 (0512, 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(0128*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(010*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的基本使用和测试代码到这边就可以结束了, 有发现什么新的关键点再继续写好了

转贴: http://www.cnblogs.com/walzer/archive/2007/01/22/626562.html

 

原创粉丝点击