内存映射文件std::allocator实现导致WM6设备冻结

9
我有一个针对 Windows Mobile 6.x 的 Visual Studio 2008 C++ 项目,需要的内存超过了32MB进程槽中可用的内存。因此,我考虑使用内存映射文件。我创建了一个标准的分配器实现,用 CreateFileMappingMapViewOfFile 替换了 new/delete。
预期的用法如下:
struct Foo
{
    char a[ 1024 ];
};

int _tmain( int argc, _TCHAR* argv[] )
{
    std::vector< boost::shared_ptr< Foo > > v;
    for( int i = 0; i < 40000; ++i )
    {
        v.push_back( boost::allocate_shared< Foo >( MappedFileAllocator< Foo >() ) );
    }
    return 0;
}

使用std::allocator,在该示例中我可以获得28197次迭代,然后出现std::bad_alloc异常。但是,使用MappedFileAllocator时,在设备完全冻结并需要重新启动之前,我可以进行32371次迭代。由于我的设备有512MB的RAM,所以我预计可以从该循环中获得更多的迭代次数。
我的MappedFileAllocator实现如下:
template< class T >
class MappedFileAllocator
{
public:
    typedef T         value_type;
    typedef size_t    size_type;
    typedef ptrdiff_t difference_type;
    typedef T*        pointer;
    typedef const T*  const_pointer;
    typedef T&        reference;
    typedef const T&  const_reference;

    pointer address( reference r ) const { return &r; };
    const_pointer address( const_reference r ) const { return &r; };

    /// convert a MappedFileAllocator<T> to a MappedFileAllocator<U>
    template< class U >
    struct rebind { typedef MappedFileAllocator< U > other; };

    MappedFileAllocator() throw() : mapped_file_( INVALID_HANDLE_VALUE ) { };

    template< class U >
    explicit MappedFileAllocator( const MappedFileAllocator< U >& other ) throw()
        : mapped_file_( INVALID_HANDLE_VALUE )
    {
        if( other.mapped_file_ != this->mapped_file_ )
        {
            ::DuplicateHandle( GetCurrentProcess(), 
                other.mapped_file_,
                GetCurrentProcess(),
                &this->mapped_file_,
                0,
                FALSE,
                DUPLICATE_SAME_ACCESS );
        }
    };

    pointer allocate( size_type n, const void* /*hint*/ = 0 )
    {
        if( n > max_size() )
           throw std::bad_alloc();

        if( n > 0 )
        {
            size_type buf_size = n * sizeof( value_type );
            mapped_file_ = ::CreateFileMapping( INVALID_HANDLE_VALUE, 
                NULL,
                PAGE_READWRITE,
                0,
                buf_size,
                L"{45E4FA7B-7B1E-4939-8CBB-811276B5D4DE}" );

            if( NULL == mapped_file_ )
                throw std::bad_alloc();

            LPVOID f = ::MapViewOfFile( mapped_file_, 
                FILE_MAP_READ | FILE_MAP_WRITE, 
                0, 
                0, 
                buf_size );

            if( NULL == f )
            {
                ::CloseHandle( mapped_file_ );
                mapped_file_ = INVALID_HANDLE_VALUE;
                throw std::bad_alloc();
            }
            return reinterpret_cast< T* >( f );
        }

        return 0;
    };

    void deallocate( pointer p, size_type n )
    {
        if( NULL != p )
        {
            ::FlushViewOfFile( p, n * sizeof( T ) );
            ::UnmapViewOfFile( p );
        }
        if( INVALID_HANDLE_VALUE != mapped_file_ )
        {
            ::CloseHandle( mapped_file_ );
            mapped_file_ = INVALID_HANDLE_VALUE;
        }
    };

    size_type max_size() const throw() 
    { 
        return std::numeric_limits< size_type >::max() / sizeof( T );
    };

    /// handle to the memory-mapped file
    HANDLE mapped_file_;

private:

    /// disallow assignment
    void operator=( const MappedFileAllocator& );

}; // class MappedFileAllocator

请问有人能够建议我在实现 MappedFileAllocator 时可能出错的地方吗?

谢谢, PaulH


检查从allocate()返回的每个指针是否对齐在某个边界上;看起来MapViewOfFile可能每次尝试映射文件时会消耗一个单页。 - vividos
@vividos - 它们在4字节边界上对齐ARM。MVOF的WM版本不需要页面对齐。http://msdn.microsoft.com/en-us/library/aa914405.aspx - PaulH
1
那我不知道问题出在哪里。下一步我会尝试使用VirtualAlloc()分配的LMA内存池,而不是使用匿名文件映射。这份文档可能会有所帮助:http://davidfindlay.org/weblog/files/ce_lma.php - vividos
@vividos - 太棒了。我会尝试一下的。谢谢! - PaulH
3个回答

1

0

您正在使用匿名文件映射(没有实际文件支持的映射)。这样做时,映射由系统页面文件支持。移动操作系统可能实际上没有页面文件,因为“硬盘”可能是闪存设备。将虚拟内存分页到闪存设备通常不是一个好主意,因为虚拟内存的性质意味着高写入量,这可能会很快烧坏闪存(特别是旧型号)。

这似乎得到了迭代次数的支持。看起来您能够映射设备总内存的约60%左右。

如果您打算写入文件映射,请小心您的闪存存储(和性能!),您可以通过打开真实文件(使用OpenFile)并映射该文件而不是在CreateFileMapping中使用INVALID_FILE_HANDLE来使其更大。


Windows Mobile的大内存区域接近1GB。最好的情况下,我只能在崩溃之前使用31MB。即使我只有512MB,31MB也远远不到设备总容量的60%-http://msdn.microsoft.com/en-us/library/bb331824.aspx。 - PaulH
切换到基于文件的实现后,我能够在设备冻结之前分配26283次(25MB)。如果需要,我可以发布该实现。 - PaulH
抱歉,我估算错了一个数量级,我以为它是310MB。无论如何,如果您无法从32MB的进程槽中获得超过31MB的空间,那么答案可能是......这就是为什么它被称为32MB的进程槽? - SoapBox
这就是为什么我转向使用MappedFileAllocator,它应该在LMA中分配内存,而LMA位于32MB进程插槽之外。我可以验证进程插槽内存没有填充,而使用std::allocator时会填充。 - PaulH

0
我刚刚获得了这个问题的“热门问题”徽章,所以我想(迟到地)发布答案。我已经用尽了可用的句柄。Windows Mobile内核中有一个与句柄分配相关的无符号短计数器正在溢出。一旦发生这种情况,设备就会冻结。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接