PHP 运行时不能自动感知硬盘拔插,因其不监听udev或/proc/mounts事件,仅在IO调用时被动响应错误;需每次操作前校验路径可用性并捕获errno。

PHP 运行时能不能自动感知硬盘拔插
不能。PHP 本身没有热插拔感知能力,fopen、file_get_contents、scandir 等函数在调用时才访问文件系统,不维护设备状态。操作系统内核负责挂载/卸载,PHP 只能被动响应错误。
常见错误现象:Warning: file_get_contents(/mnt/usb/data.json): failed to open stream: No such file or directory 或 opendir(/mnt/usb/logs): No such file or directory —— 这不是 PHP “没反应”,而是路径已失效,它照常执行,只是失败了。
- PHP 不监听
/proc/mounts或 udev 事件,也不轮询设备状态 - 即使使用
inotify扩展(如inotify_init),也只监控文件/目录变更,无法捕获底层块设备拔出 - Linux 下 USB 存储卸载后,原挂载点变成空目录,
is_dir仍返回true,但scandir报错 —— 容易误判为“目录存在所以可用”
如何安全读写外接存储上的 PHP 文件
核心是:每次 IO 操作前做轻量级可用性验证,而不是依赖“挂载即安全”。重点不是“防拔插”,而是“快速失败 + 可恢复”。
- 用
is_readable($path)+is_file($path)组合判断,比单用file_exists更准(避免挂载点残留空目录干扰) - 对关键路径(如
/mnt/backup),在业务逻辑入口加一次exec('mount | grep \'^/dev/sdb1\'')检查是否仍在挂载(注意权限和 shell 注入风险,建议白名单校验) - 写操作必须包裹
try/catch(针对fopen失败或fwrite返回false),并记录具体错误码(error_get_last()中的errno常为2(ENOENT)或19(ENODEV)) - 避免长连接式写入:不要
fopen(..., 'a')后持续fwrite数分钟;应分块写入,每次写前校验句柄有效性(is_resource($fp) && get_resource_type($fp) === 'stream')
挂载配置不当引发的 PHP 隐形故障
很多问题其实出在 Linux 挂载参数,而非 PHP 代码。例如默认 noatime 没问题,但 sync 或 noauto 会直接导致 PHP 认为路径不可写或根本找不到设备。
立即学习“PHP免费学习笔记(深入)”;
-
mount -t vfat /dev/sdb1 /mnt/usb -o uid=www-data,gid=www-data,umask=002是安全基线;漏掉uid/gid会导致 PHP 进程无权限访问(即使ls -l看起来可读) - USB 设备用
systemd-mount自动挂载时,默认启用nofail,若设备未插入,/mnt/usb会变成空目录 —— 此时is_dir为true,但所有子操作都失败 -
/etc/fstab中若写/dev/sdb1 /mnt/usb auto defaults 0 0,重启后若设备不在,挂载失败,/mnt/usb可能被创建为空目录(取决于 systemd behavior),PHP 会静默写入失败
为什么不用 inotifywait 监控挂载点变化
因为 inotifywait -m /mnt/usb 在设备拔出后会立即退出(inotify 实例失效),且无法区分“目录被 rm -rf”和“设备被拔出”。它适合监控文件变动,不适合设备生命周期管理。
- 尝试监听
/proc/mounts变更?不行 —— 它是伪文件,inotify不支持监控 procfs - 真正可行的是轮询
findmnt -n -o SOURCE /mnt/usb 2>/dev/null,但频率需控制(如每 5 秒一次),否则影响性能 - 更稳妥的做法:把设备识别逻辑下沉到 shell 脚本或 systemd path unit,由其触发 PHP 任务(如
systemd-run --scope -- php /var/www/check-usb.php),PHP 本身保持无状态
真正麻烦的不是“怎么检测拔插”,而是拔插发生在大文件写入中途——此时磁盘缓存可能丢数据,fclose 返回 true 也不代表落盘成功。这种场景下,别指望 PHP 层解决,得靠 sync 调用、挂载参数(sync)、或硬件级写保护开关。



















