13

我正在尝试使用 mmap 在 iOS 上读取和播放音频文件。它适用于最大约 400MB 的文件。但是当我尝试一个 500MB 的文件时,我得到一个 ENOMEM 错误。

char *path = [[[NSBundle mainBundle] pathForResource: @"test500MB" ofType: @"wav"] cStringUsingEncoding: [NSString defaultCStringEncoding]];
FILE *f = fopen( path, "rb" );
fseek( f, 0, SEEK_END );
int len = (int)ftell( f );
fseek( f, 0, SEEK_SET );

void *raw = mmap( 0, len, PROT_READ, MAP_SHARED, fileno( f ), 0 );

if ( raw == MAP_FAILED ) {
    printf( "MAP_FAILED. errno=%d", errno ); // Here it says 12, which is ENOMEM.
}

为什么?

我很乐意回答“700MB 是虚拟内存限制,但有时地址空间是碎片化的,所以你确实得到了 700MB 但更小的块”。(这只是猜测,我还需要一个答案)

Apple doc page about virtual memory 说:

尽管 OS X 支持后备存储,但 iOS 不支持。在 iPhone 应用程序中,已经在磁盘上的只读数据(例如代码页)只是从内存中删除并根据需要从磁盘重新加载。

这似乎证实 mmap 应该适用于大于物理内存的块,但仍然不能解释为什么我会达到如此低的限制。

更新

  • 这个答案很有趣,但 500MB 远低于它提到的 700MB 限制。
  • 这个讨论提到了连续内存。那么内存碎片可能是一个真正的问题吗?
  • 我正在使用具有 256MB 物理内存的 iPod Touch 第 4 代。
  • 我研究的重点是看看在从文件加载只读数据时是否有比“继续分配直到收到内存警告”更好的方法来管理内存。mmap似乎是解决这个问题的好方法......

更新 2

我希望 mmap 能够与新的 64 位 iOS 版本完美配合。将在我拿到 64 位设备后进行测试。

4

4 回答 4

3

在进一步调查并阅读了 John Carmack 的这篇出色的博客文章后,我的结论如下:

  • 700MB 是 iOS 上的虚拟内存限制(截至 2012 年,32 位 iOS)
  • 它可能在单个块中可用,也可能不可用;这取决于设备状态和应用行为

因此,为了可靠地映射 700MB 的文件数据,有必要将其分成更小的块。

于 2012-11-27T16:20:56.560 回答
2

我没有给你答案,但我确实在运行 6.0.1 的 iPhone 5 上测试了你的代码,并且 mmap 在 700MB ISO 文件上成功。所以我会从其他因素开始,并假设 mmap 工作正常。也许您返回的错误并不是真正由于内存造成的,或者设备本身的内存以某种方式耗尽到 mmap 失败的地方;尝试重新启动设备。根据您的 iOS 版本,我还想知道您对文件末尾的搜索是否可能导致 mmap 尝试将整个文件映射到其中;您可以尝试清理它并使用 stat 来确定文件大小,或者在映射之前关闭然后重新打开文件描述符。这些都只是想法;如果我能重现您的错误,我很乐意帮助解决它。

于 2012-11-20T22:34:01.917 回答
0

在这里使用 NSData 并且不要直接触摸 mmap。

要获得错误读取的优势,请使用 NSDataReadingMapped。NSData 在低内存情况下也释放字节

于 2012-11-17T12:29:57.073 回答
0

通常可用的物理内存量与您是否能够映射文件无关。这毕竟是我们正在谈论的虚拟内存。iOS 上的问题是,至少根据 iOS App Programming Guide,虚拟内存管理器只换出只读部分......从理论上讲,这意味着您不仅受到可用地址空间量的限制,但是,如果您使用除 PROT_READ 以外的任何东西进行映射,您也会受到可用 RAM 数量的限制。请参阅http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/TheiOSEnvironment/TheiOSEnvironment.html

尽管如此,您遇到的问题很可能是缺少足够大的连续内存来映射虚拟地址空间。据我所知,Apple 没有公布用户模式进程的内存上限。通常,地址空间的上部区域是为内核保留的。您可能只有 16 位内存可在用户模式下使用。

如果不在调试器中转储内存映射,您将看不到有许多(我在 Apple 的一个简单示例应用程序中数过 100 个)共享库(dylib)加载到进程地址空间中。这些中的每一个也都映射到其中,并且每个都将分割可用的地址空间。

在 gdb 中,您应该能够使用“info proc mappings”转储内存映射。不幸的是,在 lldb 中,我能找到的唯一等价物是“图像列表”,它只显示共享库映射,而不是数据 mmap 映射。

以这种方式使用调试器,您应该能够确定地址空间是否有一个足够大的连续块来存储您尝试映射的数据,尽管需要一些工作才能发现上限(Apple 应该发布这个!)

于 2013-02-05T16:16:27.863 回答