应使用 getmntent() 读取 /proc/mounts 并结合 statvfs() 获取挂载点空间信息,因其准确、轻量、兼容容器与 chroot 环境,且能正确处理虚拟文件系统过滤、块大小单位、路径可访问性及大容量溢出等问题。

Linux 下 C++ 程序要可靠获取硬盘分区的挂载点和剩余空间,statvfs() 是最直接、最轻量、且无需依赖额外库的方案;getmntent() 配合 statvfs() 才能完整枚举所有挂载点,仅靠 df 命令或 libudev 会漏掉 tmpfs、bind mount 或容器挂载等场景。
为什么不用 system("df -P") 解析输出
看似简单,但实际踩坑极多:df -P 输出格式在不同发行版(如 Alpine vs RHEL)或 locale 下可能变动字段顺序;空格出现在挂载点路径中时(如 /mnt/my disk)会导致解析错位;无法区分 bind mount 和真实块设备挂载;且每次调用都 fork 新进程,性能差、不可控。
更关键的是:它绕过了内核真实的挂载视图,比如 chroot 环境或容器中,df 可能显示宿主机信息,而 getmntent() 读取的是当前进程看到的 /proc/mounts,语义准确。
如何用 getmntent() + statvfs() 安全遍历所有挂载点
核心逻辑是先读取 /proc/mounts(或 /etc/mtab 作为 fallback),过滤出类型为 ext4、xfs、btrfs、vfat 等实际存储文件系统的条目,再对每个 mnt_dir 调用 statvfs() 获取空间数据。
立即学习“C++免费学习笔记(深入)”;
-
setmntent("/proc/mounts", "r")比直接 fopen 更健壮,能正确处理注释与换行 - 必须检查
struct mntent的mnt_type字段,跳过proc、sysfs、debugfs、devtmpfs等虚拟文件系统(它们的f_bavail无意义) - 调用
statvfs(mnt_dir, &buf)前需确保mnt_dir可访问(access(mnt_dir, F_OK) == 0),否则statvfs返回 -1 且errno为ENOTCONN或ESTALE(常见于 NFS 临时断连或 umount 中的路径) -
buf.f_frsize * buf.f_bavail是可用字节数(考虑 root 预留空间),不是f_bfree—— 后者包含 root 保留块,普通用户不可用
statvfs 结构体关键字段含义与单位陷阱
statvfs 返回的数值全是“文件系统块数”,不是字节,且块大小由 f_frsize(首选)或 f_bsize(IO 优化块大小,可能不等于基本块)决定。误用 f_bsize 在某些 XFS 或 Btrfs 上会导致容量计算偏差。
常见错误写法:buf.f_bavail * buf.f_bsize → 错。正确是:buf.f_bavail * buf.f_frsize。
另外注意:
-
f_files和f_ffree是 inode 总数与空闲数,和空间无关,别混淆 -
f_flag & ST_RDONLY可判断是否只读挂载,但不能替代access(path, W_OK)实际测试写权限 - 32 位程序在大容量文件系统(>2TiB)上若用
long存储f_blocks可能溢出,应统一用uint64_t接收计算结果
一个最小可运行的 C++ 示例(g++ -std=c++17)
#include <mntent.h>
#include <sys/statvfs.h>
#include <unistd.h>
#include <cstdio>
#include <string>
#include <iostream>
<p>int main() {
FILE* fp = setmntent("/proc/mounts", "r");
if (!fp) return 1;</p><pre class="brush:php;toolbar:false;">struct mntent* ent;
while ((ent = getmntent(fp)) != nullptr) {
// 过滤非磁盘文件系统
if (std::string(ent->mnt_type).find("ext") == 0 ||
std::string(ent->mnt_type) == "xfs" ||
std::string(ent->mnt_type) == "btrfs" ||
std::string(ent->mnt_type) == "vfat") {
struct statvfs buf;
if (statvfs(ent->mnt_dir, &buf) == 0 && access(ent->mnt_dir, F_OK) == 0) {
uint64_t avail = static_cast<uint64_t>(buf.f_bavail) * buf.f_frsize;
uint64_t total = static_cast<uint64_t>(buf.f_blocks) * buf.f_frsize;
std::cout << ent->mnt_dir << "\t"
<< total / (1024*1024) << " MiB\t"
<< avail / (1024*1024) << " MiB\n";
}
}
}
endmntent(fp);
return 0;}
这个例子没做内存安全封装(比如 std::string 构造 mnt_dir),是因为 getmntent() 返回的指针生命周期绑定到 FILE*,且 /proc/mounts 行长有限;过度拷贝反而掩盖了原始数据来源的约束条件——真正上线时,得根据是否多线程调用决定是否加锁或改用 getmntent_r()。


















