Linux下用statvfs()获取文件系统块配额与使用率最轻量通用,必须用f_frsize计算总空间(total=fs.f_blocks*fs.f_frsize),监控告警应基于f_bavail(普通用户可用块数)而非f_bfree(含root预留空间)。

Linux 下用 statvfs() 读取文件系统块配额和使用率
Linux 不提供“用户级配额”之外的全局块级硬性配额(比如 cgroup v1 的 blkio.weight 是 I/O 调度权重,不是存储容量限制),statvfs() 返回的是挂载点所在设备的**实际可用块数与总块数**,也就是你真正能写多少数据——这才是 C++ 程序该依赖的底层依据。
常见错误是直接用 df -h 输出去解析,但 shell 工具会做单位换算、四舍五入、甚至受 locale 影响;而 statvfs() 返回原始 uint64_t 块计数,无损且线程安全。
-
f_frsize是文件系统“基本块大小”(fundamental block size),不是f_bsize(I/O 优化块大小),计算容量必须用f_frsize - 已用块数 =
fs.f_blocks - fs.f_bfree;注意f_bfree是非特权用户可用空闲块,f_bavail才是普通进程实际可写块(预留 5% 给 root) - 若需“磁盘使用率”,应基于
f_blocks和f_bavail计算:(f_blocks - f_bavail) * 100.0 / f_blocks,避免除零需先检查f_blocks > 0
struct statvfs fs;
if (statvfs("/path/to/dir", &fs) == 0) {
uint64_t total = fs.f_frsize * fs.f_blocks;
uint64_t avail = fs.f_frsize * fs.f_bavail;
double usage_pct = (fs.f_blocks > 0)
? (fs.f_blocks - fs.f_bavail) * 100.0 / fs.f_blocks
: 0.0;
}Windows 上调用 GetDiskFreeSpaceEx() 获取真实可用空间
Windows 没有类 Unix 的 vfs 抽象层,GetDiskFreeSpaceEx() 是唯一可靠方式。它返回三个值:*lpFreeBytesAvailable(当前用户可用字节数)、*lpTotalNumberOfBytes、*lpTotalNumberOfFreeBytes(所有用户总空闲)。别误用后两者算“使用率”——它们不考虑配额(如 NTFS 配额策略)或磁盘压缩。
关键点:该函数只接受**驱动器根路径**(如 "C:\"),传入子目录会失败;且返回值是 ULARGE_INTEGER,必须用 .QuadPart 成员访问 64 位整数。
立即学习“C++免费学习笔记(深入)”;
- 若程序运行在启用了 NTFS 配额的卷上,
lpFreeBytesAvailable自动扣减该用户的配额余量,无需额外判断 - 返回值为 0 表示调用失败(如路径无效或权限不足),不能直接当“0 可用”处理;需用
GetLastError()区分ERROR_ACCESS_DENIED和ERROR_INVALID_DRIVE - 不要用
GetDiskFreeSpace()(旧版 32 位接口),它会在 >2TB 卷上截断,导致结果错误
跨平台封装时绕过 std::filesystem::space() 的陷阱
C++17 的 std::filesystem::space() 看似完美,但它在不同标准库实现中行为不一致:libstdc++(GCC)内部调用 statvfs(),而 MSVC 的实现曾长期返回 uint64_t(-1) 表示“未知”(直到 VS2019 16.8+ 才修复)。更严重的是,它**不区分 f_bfree 和 f_bavail**,返回的 available 字段在 Linux 上等于 f_bfree(即含 root 预留空间),而非进程实际能写的 f_bavail。
- 生产环境建议弃用
std::filesystem::space(),自己封装statvfs()/GetDiskFreeSpaceEx() - 若必须用,至少加运行时检测:对 Linux 路径,手动再调一次
statvfs()校验f_bavail是否显著小于f_bfree(例如差值 >1GB),若是则说明存在预留,应以f_bavail为准 - 注意
std::filesystem::space()对符号链接默认解析目标,若需统计挂载点本身,得先std::filesystem::canonical()再取父目录的 space
配额策略未生效时的典型误判现象
很多开发者看到 statvfs().f_bavail 还剩 20GB,就认为“还能写”,结果 write() 突然返回 ENOSPC。这不是 API 错,而是忽略了两个现实约束:
- 文件系统 journal(如 ext4)或 metadata 预分配会占用额外块,尤其小文件密集写入时,可用块可能比
statvfs()显示少几百 MB - 容器或虚拟化环境(如 Docker overlay2、LXC)中,宿主机的
statvfs()返回的是底层块设备状态,但容器内挂载的 upperdir 可能已达到 overlayFS 自身的 inode 或块限制(需查/proc/mounts中对应 mount 的 options) - 某些 NAS 或分布式文件系统(如 NFSv4.1+、CephFS)支持服务端配额,但
statvfs()不反映这些策略——它只读取本地挂载信息,此时必须走专有协议(如 NFS 的GETFH+GETATTR)或管理 API
真要严谨判断“此刻能否写入 N 字节”,唯一的办法是:在目标路径下 open(O_TMPFILE) 创建临时文件,fallocate() 预占空间,成功后再 linkat() 落地——绕过一切缓存与预测,直击存储栈最底层。


















