statvfs 是获取挂载点剩余空间最可靠的跨平台方案,需用 realpath 归一化路径后调用,以 f_frsize 而非 f_bsize 计算容量,并区分 f_bfree 与 f_bavail。

用 statvfs 获取挂载点剩余空间最可靠
Linux/macOS 下想准确拿到某个挂载点(比如 /home 或 /mnt/data)的剩余空间、总大小、可用 inode 数,statvfs 是 C++ 原生可调用且跨平台性最好的方案。它不依赖 shell 命令解析,也不受 locale 或输出格式变动影响。
常见错误是直接对文件路径(如 "/mnt/data/file.txt")调用 statvfs —— 这会返回该文件所在挂载点的信息,但若路径不存在或权限不足,statvfs 会失败。正确做法是先用 realpath 归一化路径,再传入其挂载根目录(或任意该挂载下的存在路径):
#include <sys/statvfs.h>
#include <libgen.h>
#include <cstring>
<p>struct statvfs fs;
if (statvfs("/mnt/data", &fs) == 0) {
uint64_t total = static_cast<uint64_t>(fs.f_blocks) <em> fs.f_frsize;
uint64_t free = static_cast<uint64_t>(fs.f_bfree) </em> fs.f_frsize;
uint64_t avail = static_cast<uint64_t>(fs.f_bavail) * fs.f_frsize;
}
-
f_blocks×f_frsize是总空间(不是f_bsize,后者是 I/O 块大小,不可用于容量计算) -
f_bfree是所有用户可见的空闲块数;f_bavail是非 root 用户实际可用块数(考虑了保留空间) - Windows 不支持
statvfs,需改用GetDiskFreeSpaceEx,别硬套 POSIX 接口
遍历所有挂载点要用 /proc/mounts(Linux)或 getmntinfo(macOS)
想自动获取系统上所有挂载点列表(类似 mount 命令输出),不能靠猜测路径,得读系统接口。Linux 下直接解析 /proc/mounts 最轻量;macOS 必须用 getmntinfo,因为没有 /proc。
Linux 示例(跳过伪文件系统):
立即学习“C++免费学习笔记(深入)”;
#include <fstream>
#include <string>
#include <vector>
<p>std::vector<std::string> get_mount_points() {
std::vector<std::string> mounts;
std::ifstream f("/proc/mounts");
std::string line;
while (std::getline(f, line)) {
std::istringstream iss(line);
std::string dev, path, fstype;
if (iss >> dev >> path >> fstype) {
if (fstype != "proc" && fstype != "sysfs" && fstype != "debugfs")
mounts.push_back(path);
}
}
return mounts;
}
- 过滤掉
proc、sysfs、devtmpfs等虚拟文件系统,它们无磁盘空间概念 -
/proc/mounts可能包含重复条目(如 bind mount),按path去重更稳妥 - macOS 上必须用
getmntinfo(&mntbuf, MNT_WAIT),返回的是struct statfs *数组,字段名和语义与statvfs不同(例如用f_bavail而非f_bavail)
注意 f_frsize 和 f_bsize 的区别,否则容量算错
这是最容易翻车的地方:很多代码直接用 f_bsize 乘以 f_blocks,结果比 df 输出小得多。根本原因是 f_bsize 是“文件系统建议的 I/O 块大小”,而真实容量单位是 f_frsize(fundamental filesystem block size)。
-
f_frsize才是计算总/空闲/可用字节数的正确基数 -
f_bsize仅用于优化 read/write 缓冲区大小,和容量无关 - 某些旧文件系统(如 ext2)可能让
f_frsize == 0,此时应 fallback 到f_bsize,但现代系统基本不用处理 - 验证方法:对比
df --block-size=1 /path | tail -1的数值,是否与f_blocks * f_frsize一致
C++ 封装时别暴露裸指针或 errno 逻辑
写库函数时,别把 statvfs 的 int 返回值和全局 errno 暴露给上层。用户只关心“这个路径的空间是多少”或“失败原因是什么”。
推荐返回结构体 + std::optional:
struct DiskUsage {
uint64_t total = 0;
uint64_t used = 0;
uint64_t available = 0; // for non-root
uint64_t files_total = 0;
uint64_t files_free = 0;
};
<p>std::optional<DiskUsage> get_disk_usage(const std::string& path) {
struct statvfs fs;
if (statvfs(path.c_str(), &fs) != 0) {
return std::nullopt; // 或 throw std::system_error(errno, std::generic_category());
}
return DiskUsage{
static_cast<uint64_t>(fs.f_blocks) <em> fs.f_frsize,
(static_cast<uint64_t>(fs.f_blocks) - fs.f_bfree) </em> fs.f_frsize,
static_cast<uint64_t>(fs.f_bavail) * fs.f_frsize,
fs.f_files,
fs.f_ffree
};
}
- 避免用异常传递系统调用失败(除非明确要求异常语义),
std::optional更轻量可控 - 字段命名用
available而非free,强调这是普通用户可用值(f_bavail),和df的Avail列对齐 - 不要在库中做路径合法性校验(如检查是否为目录),那是调用方的责任;
statvfs本身对文件/目录/挂载点路径行为一致
真正难的不是调哪个函数,而是意识到不同系统对“可用空间”的定义不同,以及 /proc/mounts 在容器里可能被挂载为只读或不可见——这些边界情况比 API 调用本身更消耗调试时间。


















