PHP 本身不支持真正的 mmap,必须依赖 PECL 扩展 ext-mmap;Composer 包如 ramsey/mmap 仅是命名封装,底层仍为 fread,无法实现零拷贝或跳过 I/O 栈;真正 mmap 需 C 层调用系统 API,映射后须校验越界、防截断、正确释放。

PHP 本身不支持真正的 mmap 内存映射,所谓“PHP 使用 mmap 优化大文件读取”必须依赖 PECL 扩展 ext-mmap,而非任何 Composer 包。纯 PHP 代码无法绕过系统调用、无法操作页表、也无法建立虚拟内存到文件块的直接映射——这是操作系统内核级能力,只能由 C 扩展实现。
为什么不能靠 Composer 包实现 mmap
ramsey/mmap、symfony/filesystem 等库只是命名空间封装或流式读写增强,底层仍是 fread() 或 file_get_contents(),每次调用都触发系统调用 + 内核态/用户态切换 + 两次内存拷贝(磁盘→页缓存→用户缓冲区)。它们不提供零拷贝、不跳过 I/O 栈、性能不会优于原生 fread。
- 真正 mmap 需调用 Linux 的
mmap(2)或 Windows 的CreateFileMapping,这些必须在 C 层完成 - ext-mmap 是唯一提供
mmap()、msync()、munmap()函数的 PHP 扩展 - 安装方式是
pecl install mmap,并在php.ini中启用extension=mmap - 使用前务必检查:
function_exists('mmap'),否则运行时报Fatal error: Call to undefined function mmap()
只读大文件的正确 mmap 流程
对 GB 级只读文件(如日志、二进制索引、模型权重),mmap 可显著减少系统调用次数、避免重复加载、并允许多进程共享物理页。
- 用
open($path, O_RDONLY)获取文件描述符,确保文件存在且大小 > 0 - 调用
fstat()获取真实大小,$length必须 ≤st_size;传入超长值虽可能成功,但越界访问会触发SIGBUS - 映射时指定
PROT_READ | MAP_PRIVATE:前者禁止写,后者避免写时复制开销 - 返回指针为 C 风格地址(非 PHP 字符串),需用
substr()或ord()/unpack()按偏移提取数据,不可直接 echo 或 json_encode - 使用完毕必须调用
munmap($ptr),否则内存泄漏;PHP 脚本结束不自动释放 mmap 区域
常见崩溃与规避方式
SIGBUS 是 mmap 场景下最典型的错误,不是代码逻辑错,而是内存访问违规。
立即学习“PHP免费学习笔记(深入)”;
-
越界读:访问
$ptr + $offset时,$offset ≥ $length→ 触发SIGBUS。必须在每次访问前校验:if ($offset + $need_len > $length) { /* error */ } -
文件被截断:映射后外部执行
truncate或ftruncate()→ 映射区域部分失效 → 访问即崩。只读场景建议设文件为不可修改(chown root:root & chmod 444) -
未 flush 就退出:MAP_SHARED 写入后未调
msync($ptr, $length, MS_SYNC)→ 进程退出时修改丢失 -
混用 fclose 和 munmap:先
fclose($fp)再munmap($ptr)可能失败;应先munmap,再fclose
是否值得上 mmap?看场景
mmap 不是银弹。它适合:
- 多个 PHP worker 进程反复读同一份只读大文件(如配置、词典、模型),可共享物理内存
- 需要随机跳转访问(如按 offset 查某条记录、解析头部+跳读 body)
- 文件 ≥ 50MB,且单次只读少量数据(如解析 header 后取某几段)
不适合:
- 小文件(
- 顺序逐行读日志:
file()+foreach或stream_get_line()更简单稳定 - Web 请求短生命周期脚本:mmap 初始化 + 释放耗时占比高,GC 不感知 mmap 区域,易遗漏清理



















