
从一个不寻常的I/O卡顿入手,我们团队发现了一个苹果APFS文件系统的重要bug。近期,有用户反馈遇到了频繁的卡顿问题,尤其是在进行微信刷朋友圈和查看聊天等操作时,主线程经常卡在上百毫秒的I/O系统调用上,这种情况的底层接口一般都没有进行大量的I/O操作。
一、问题分析
我们首先对问题进行定位,寻找关键堆栈信息。从堆栈信息来看,只是在很常规的操作,如视频号卡片列表滑动时,触发下载图片和查图片本地缓存的逻辑。通过access接口同步查本地图片是否存在,有则直接展示,否则从网络下载图片。在实际测试过程中,我们发现当子目录包含过多文件时,就会出现I/O操作卡顿的现象。
通过构建特定场景下的测试代码,我们成功复现了这个问题,并发现当对特定目录下写入大约10万个文件后,频繁地进行access等I/O操作会导致平均耗时远超正常预期。通过Instruments系统追踪分析,我们发现问题的主要原因是等待锁的时间过长。
二、深入研究
为了更深入地了解问题的原因,我们借助dtrace工具进行分析。通过收集到的dtrace数据,我们发现大概率是kernel层的APFS相关的锁出了问题。通过Hopper分析APFS的系统支持代码,我们发现每次执行rename或access接口时,都会尝试获取一种目录锁。在超大目录遍历时,这个加锁导致的等待问题会急剧扩大,导致锁等待超时,最终可能导致并发I/O速度骤降。
三、解决问题
针对这个问题,我们找到了稳定的复现方法,并确认了用户遇到的情况与我们构造的demo基本一致。解决问题的办法是将目录拆分为二级目录,以缓解单目录文件过多的情况。经过测试,拆分后的目录结构再也没有出现类似的并发I/O卡顿情况。我们已经将这个问题反馈给苹果,希望苹果能够进一步提升APFS的性能。
四、其他注意事项
除了APFS的问题,我们还发现苹果的文件操作中存在一些其他需要注意的坑。例如,虽然苹果建议在tmp/目录存储临时文件,并声称系统会自动删除,但实际情况并非如此。WebKit和NSURLCache在默认或自定义磁盘缓存路径时,如果设置不当可能会导致占用大量存储空间。密集删除文件时也容易导宕机I/O性能下降过快。移动端app在设计存储结构时需要注意合理分层分级管理文件,避免单个文件夹或文件过大,并定时清理临时缓存目录。
通过对System Trace数据的分析,我们进一步证实了APFS内部存在某种目录锁的结构,在超大目录遍历时会导致锁等待超时,从而引发并发I/O性能下降的问题。为了避免这种情况导致的I/O性能问题,移动端app需要合理设计存储结构,并定时清理临时缓存目录以优化存储占用和提升I/O效率。苹果自iOS 10.3版本开始引入了全新的文件系统APFS(Apple File System),在此之前,HFS+一直是iOS和macOS的默认文件系统。为了理解应用程序如何从SSD等存储介质上读写文件,我们需要深入了解VFS(Virtual File System)。
VFS是一个重要的系统组件,它统一并抽象了不同文件系统的接口。通过VFS,用户可以使用统一的系统调用接口访问不同文件系统上的文件,无论这些文件存储在何种存储介质上。VFS可以大致分为三层:vfstbllist用于管理不同的文件系统,mount负责文件系统的挂载,而vnode则代表文件和文件夹等对象。
在XNU中,vfstbllist是一个关键组件,用于注册和管理多个文件系统。典型的vfstbllist包含了不同的文件系统操作设置,例如HFS/HFS+文件系统和Sun-compatible Network File System等。内核可以通过vfs_fsadd等接口动态加载不同的内核扩展,以支持新的文件系统。
文件系统需要被mount挂载后才能被访问。macOS会自动从/System/Library/FileSystems里找到对应的内核扩展并挂载文件系统。对于不支持的文件系统,则需要加载相应的内核扩展以支持。
vnode是VFS中最重要的部分,它代表一个文件或特定的文件系统对象。一个vnode通常对应实际文件系统中的inode。
HFS+(Hierarchical File System Plus)自19的Mac OS 8.1开始被引入,直至iOS 10.3之前一直作为默认文件系统。HFS+更有效地利用磁盘空间,使用Unicode存储文件编码,提供了更先进的文件系统支持。它通过引入Catalog File来存储目录结构,虽然提高了文件系统的查找速度,但在超大并况下可能会因为频繁访问Catalog File而导致性能下降。
APFS作为苹果推出的最新文件系统,是HFS+的继任者,解决了HFS+在现代文件系统上的不足。APFS为SSD设计和优化,新增了cloning、snapshots、space sharing、fast directory sizing、atomic safe-save、sparse files等功能,填补了苹果在现代文件系统能力上的空白。
当开发者触发如rename系统调用时,VFS会相应地进行文件操作。如果目标文件所在的分区是APFS,则会触发相应的APFS操作;如果是HFS+分区,则会触发HFS+的相关操作,最终完成文件重命名等操作。整体上,苹果的APFS和安卓设备的F2FS都是为移动设备优化的文件系统,二者在设计上有许多相似之处。
