【问题标题】:Why does mmap fail on iOS?为什么 iOS 上的 mmap 会失败?
【发布时间】:2012-11-16 22:45:43
【问题描述】:

我正在尝试使用 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 文档页面说:

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

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

更新

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

更新 2

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

【问题讨论】:

  • 嗯,一个 500MB 的文件几乎是 iPhone 4 上实际可用的可用 RAM 的两倍...您期望什么? mmap() 不是魔法。
  • @H2CO3 我希望它在访问 mmapped 区域时按需将文件数据分页到 RAM 中。这不是 mmap 的作用吗?
  • 这是一个实现细节你不应该依赖它,b。你不应该有任何期望,c。可以通过阅读libSystem源代码的相关部分来查找。
  • @H2CO3 256MB iPod Touch 上的 400MB 文件不会失败。
  • “没有期望”关于 mmap() 对我来说听起来不对。 mmap() 存在的全部原因(与 read() 相反)是为了让文件可以按需分页,并在 VM 系统认为合理时从物理内存中删除。即使只有一个可用的 RAM 物理页,也应该可以 mmap() 一个大文件。但是,如果您 mmap() 的范围大于最大的连续虚拟内存块,您将失败——您可能应该一次映射较小的文件段来解决这个问题。

标签: ios memory-management mmap


【解决方案1】:

经过进一步调查并阅读此excellent blog post by John Carmack,以下是我的结论:

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

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

【讨论】:

  • 好吧。链接现在重定向到贝塞斯达。
  • @matrixugly 这可能是因为年龄检查。输入年龄后,您可以重新加载链接,它将起作用。我刚查了一下,博客条目还在。
  • 块应该有多小?
  • @adib 不太确定,对于 32 位操作系统,我会选择 100-200MB。这在 64 位系统上可能不再是问题了。
【解决方案2】:

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

【讨论】:

  • 感谢您实际尝试并报告您的结果。我认为重启设备可能会有所帮助,但显然我不能要求我的用户这样做。
  • 至于可能导致 mmap 失败的查找,我不认为是这种情况 - 我尝试硬编码文件大小而不查找相同的结果。
【解决方案3】:

这里使用NSData,不要直接接触mmap。

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

【讨论】:

    【解决方案4】:

    通常可用的物理内存量与您是否能够映射文件无关。这毕竟是我们正在谈论的虚拟内存。 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 应该发布此!)

    【讨论】:

    • 事实上,Apple 首次公布了在 iOS 设备上运行的应用程序的内存上限:在第三代 iPad 上为 750MB。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-11
    • 2021-10-10
    • 2013-01-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多