statvfs-statfs系统调用及示例

我们来深入学习 statfs 和 statvfs 这两个系统调用。这两个函数功能非常相似,主要区别在于标准化和可移植性。

1. 函数介绍

在 Linux 系统中,文件和目录都存储在各种各样的文件系统之上(如 ext4, XFS, Btrfs, tmpfs 等)。每个文件系统都有自己的特性,比如总容量多大、现在用了多少、还剩多少空间、支持的文件名最长多少字符等等。

statfs 和 statvfs 系统调用的作用就是查询指定路径所在文件系统的各种统计信息和属性

你可以把它们想象成“文件系统信息查询器”。你随便给它一个路径(比如 /home/tmp/mnt/my_usb),它就能告诉你这个路径所在的那个磁盘分区(文件系统)的详细情况。

简单来说,statfs 和 statvfs 就是让你用程序来查看某个磁盘分区或挂载点的“健康报告”和“容量信息”。

2. 函数原型

// statfs: Linux 特定的系统调用
#include <sys/vfs.h>    // 包含系统调用声明 (在某些系统上可能是 <sys/statfs.h>)
// #include <sys/statfs.h> // 备选包含

int statfs(const char *path, struct statfs *buf);
int fstatfs(int fd, struct statfs *buf); // 通过已打开的文件描述符查询


// statvfs: POSIX 标准定义的系统调用,可移植性更好
#include <sys/statvfs.h> // 包含系统调用声明

int statvfs(const char *path, struct statvfs *buf);
int fstatvfs(int fd, struct statvfs *buf); // 通过已打开的文件描述符查询

3. 功能

获取指定路径 (path) 或文件描述符 (fd) 所在文件系统的统计信息,并将结果存储在相应的结构体 (buf) 中。

4. 参数

两者参数完全相同:

  • path:
    • const char * 类型。
    • 指向一个以 null 结尾的字符串,表示文件系统中的任意一个路径名。函数会查询这个路径所在的文件系统的统计信息。
  • fd:
    • int 类型。
    • 一个已打开文件的有效文件描述符。fstatfs/fstatvfs 会查询该文件描述符对应的文件所在的文件系统的统计信息。
  • buf:
    • struct statfs * 或 struct statvfs * 类型。
    • 一个指向相应结构体的指针。函数调用成功后,会将查询到的文件系统信息填充到这个结构体中。

5. 返回值

两者返回值也相同:

  • 成功: 返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

两者共享许多相同的错误码:

  • EACCES: (对于 statfs/statvfs) 搜索 path 中的一个或多个组件时权限不足。
  • EFAULTpath 或 buf 指向了调用进程无法访问的内存地址。
  • EIO: I/O 错误(例如,读取文件系统超级块失败)。
  • ELOOP: 解析 path 时遇到符号链接循环。
  • ENAMETOOLONGpath 太长。
  • ENOENTpath 指定的文件或目录不存在。
  • ENOMEM: 内核内存不足。
  • ENOSYS: 系统不支持该调用。
  • ENOTDIRpath 的某个前缀不是目录。
  • EOVERFLOW: 结构体中的某些值溢出。
  • EBADF: (对于 fstatfs/fstatvfsfd 不是有效的文件描述符。

7. struct statfs 和 struct statvfs 结构体

这两个结构体包含的信息非常相似,但 struct statvfs 是 POSIX 标准化的,字段名更规范。

struct statfs (Linux 特定)

struct statfs {
    __fsword_t f_type;    /* 文件系统类型 (Magic Number) */
    __fsword_t f_bsize;   /* 文件系统最优传输块大小 (Optimal transfer block size) */
    fsblkcnt_t f_blocks;  /* 文件系统中的总块数 */
    fsblkcnt_t f_bfree;   /* 文件系统中空闲的块数 */
    fsblkcnt_t f_bavail;  /* 对非特权用户可用的空闲块数 (可能小于 f_bfree) */
    fsfilcnt_t f_files;   /* 文件系统中的总 Inode 数 */
    fsfilcnt_t f_ffree;   /* 文件系统中空闲的 Inode 数 */
    fsid_t     f_fsid;    /* 文件系统 ID */
    __fsword_t f_namelen; /* 文件名最大长度 */
    __fsword_t f_frsize;  /* 片段大小 (Fragment size) */
    __fsword_t f_flags;   /* 文件系统挂载标志 */
    ...                   /* 可能还有其他字段 */
};

struct statvfs (POSIX 标准)

struct statvfs {
    unsigned long  f_bsize;    /* 文件系统块大小 (用于统计) */
    unsigned long  f_frsize;   /* 片段大小 (Fragment size) */
    fsblkcnt_t     f_blocks;   /* 文件系统中的总片段数 (f_frsize) */
    fsblkcnt_t     f_bfree;    /* 文件系统中空闲的片段数 */
    fsblkcnt_t     f_bavail;   /* 对非特权用户可用的片段数 */
    fsfilcnt_t     f_files;    /* 文件系统中的总 Inode 数 */
    fsfilcnt_t     f_ffree;    /* 文件系统中空闲的 Inode 数 */
    fsfilcnt_t     f_favail;   /* 对非特权用户可用的空闲 Inode 数 */
    unsigned long  f_fsid;     /* 文件系统 ID */
    unsigned long  f_flag;     /* 挂载标志 */
    unsigned long  f_namemax;  /* 最大文件名长度 */
};

关键字段解释 (两个结构体通用概念):

  • f_bsize / f_frsizef_frsize 是文件系统的基本片段大小。f_bsize 是推荐用于 I/O 操作的块大小(可能与 f_frsize 相同或不同)。计算总大小时通常用 f_blocks * f_frsize
  • f_blocks: 文件系统总共有多少个片段 (f_frsize)。
  • f_bfree: 文件系统总共有多少个空闲片段。
  • f_bavail: 对普通用户(非 root)来说,实际还可以使用的空闲片段数量。有些文件系统会保留一部分空间给 root 用户。
  • f_files: 文件系统总共包含多少个 Inode(索引节点)。
  • f_ffree: 文件系统中空闲的 Inode 数量。
  • f_type / f_fsid: 文件系统的类型标识符或 ID。
  • f_namelen / f_namemax: 文件系统支持的文件名或目录名的最大长度。

8. statfs vs statvfs:该如何选择?

  • statfs:
    • 优点:是 Linux 原生接口,可能包含一些 statvfs 没有的 Linux 特定信息(如 f_type 魔数)。
    • 缺点仅限 Linux。如果你的代码需要在其他 Unix 或类 Unix 系统(如 BSD, macOS)上编译运行,statfs 可能不存在或行为不同。
  • statvfs:
    • 优点POSIX 标准。这意味着它在所有符合 POSIX 标准的系统上都可用,具有最佳的可移植性
    • 缺点:可能缺少一些 Linux 特定的详细信息。

对于 Linux 编程小白的建议

  1. 优先使用 statvfs。它是标准的、可移植的。除非你有特殊需求必须使用 statfs 的特定功能,否则 statvfs 是更好的选择。
  2. 如果你确定只在 Linux 上运行,并且需要访问 f_type 这样的 Linux 特定信息,可以使用 statfs

9. 示例代码

下面的示例演示了如何使用 statfs 和 statvfs 来查询文件系统信息,并比较它们的输出。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/vfs.h>      // 包含 statfs
#include <sys/statvfs.h>  // 包含 statvfs
#include <string.h>
#include <errno.h>
#include <fcntl.h> // 包含 open

// 辅助函数:打印人类可读的大小
void print_human_readable(double bytes, const char* prefix) {
    const char *units[] = {"B", "KiB", "MiB", "GiB", "TiB"};
    int unit_index = 0;
    double size_to_print = bytes;
    while (size_to_print >= 1024 && unit_index < 4) {
        size_to_print /= 1024;
        unit_index++;
    }
    printf("%s%.2f %s", prefix, size_to_print, units[unit_index]);
}

// 使用 statfs 打印信息
void print_fs_info_statfs(const char* path, const struct statfs *sfs) {
    printf("=== Filesystem information for '%s' (via statfs) ===\n", path);
    printf("  Optimal I/O Block Size: %ld bytes\n", (long)sfs->f_bsize);
    printf("  Fragment Size:          %ld bytes\n", (long)sfs->f_frsize);
    printf("  Total Fragments:        %ld\n", (long)sfs->f_blocks);
    printf("  Free Fragments:         %ld\n", (long)sfs->f_bfree);
    printf("  Available Fragments:    %ld (for non-root users)\n", (long)sfs->f_bavail);
    printf("  Total Inodes:           %ld\n", (long)sfs->f_files);
    printf("  Free Inodes:            %ld\n", (long)sfs->f_ffree);
    printf("  Filesystem ID:          %d:%d\n", (int)sfs->f_fsid.__val[0], (int)sfs->f_fsid.__val[1]);
    printf("  Maximum Filename Length: %ld\n", (long)sfs->f_namelen);
    printf("  Mount Flags:            0x%lx\n", (unsigned long)sfs->f_flags);

    // 计算并打印人类可读的空间大小
    double total_bytes = (double)sfs->f_blocks * sfs->f_frsize;
    double free_bytes = (double)sfs->f_bfree * sfs->f_frsize;
    double avail_bytes = (double)sfs->f_bavail * sfs->f_frsize;

    printf("  Total Size:             ");
    print_human_readable(total_bytes, "");
    printf("\n");
    printf("  Available Size:         ");
    print_human_readable(avail_bytes, "");
    printf("\n");

    // 识别文件系统类型 (仅列举几个常见类型)
    switch (sfs->f_type) {
        case 0xEF53: // EXT2/3/4
            printf("  Filesystem Type:        ext2/ext3/ext4\n");
            break;
        case 0x6969: // NFS
            printf("  Filesystem Type:        NFS\n");
            break;
        case 0x517B: // SMB/CIFS
            printf("  Filesystem Type:        SMB/CIFS\n");
            break;
        case 0x52654973: // ReiserFS
            printf("  Filesystem Type:        ReiserFS\n");
            break;
        case 0x5346544E: // NTFS
            printf("  Filesystem Type:        NTFS\n");
            break;
        case 0x4d44: // FAT
            printf("  Filesystem Type:        FAT\n");
            break;
        case 0x01021994: // TMPFS
            printf("  Filesystem Type:        tmpfs\n");
            break;
        case 0x9123683E: // Btrfs
            printf("  Filesystem Type:        Btrfs\n");
            break;
        default:
            printf("  Filesystem Type:        Unknown (Magic: 0x%lx)\n", (unsigned long)sfs->f_type);
            break;
    }
    printf("\n");
}

// 使用 statvfs 打印信息
void print_fs_info_statvfs(const char* path, const struct statvfs *svfs) {
    printf("=== Filesystem information for '%s' (via statvfs) ===\n", path);
    printf("  Filesystem Block Size:  %ld bytes\n", (long)svfs->f_bsize);
    printf("  Fragment Size:          %ld bytes\n", (long)svfs->f_frsize);
    printf("  Total Fragments:        %ld\n", (long)svfs->f_blocks);
    printf("  Free Fragments:         %ld\n", (long)svfs->f_bfree);
    printf("  Available Fragments:    %ld (for non-root users)\n", (long)svfs->f_bavail);
    printf("  Available Fragments (non-root): %ld\n", (long)svfs->f_bavail); // Same as f_bavail in most cases
    printf("  Total Inodes:           %ld\n", (long)svfs->f_files);
    printf("  Free Inodes:            %ld\n", (long)svfs->f_ffree);
    printf("  Available Inodes (non-root): %ld\n", (long)svfs->f_favail);
    printf("  Filesystem ID:          %ld\n", (unsigned long)svfs->f_fsid);
    printf("  Maximum Filename Length: %ld\n", (long)svfs->f_namemax);
    printf("  Mount Flags:            0x%lx\n", svfs->f_flag);

    // 计算并打印人类可读的空间大小
    double total_bytes = (double)svfs->f_blocks * svfs->f_frsize;
    double free_bytes = (double)svfs->f_bfree * svfs->f_frsize;
    double avail_bytes = (double)svfs->f_bavail * svfs->f_frsize;

    printf("  Total Size:             ");
    print_human_readable(total_bytes, "");
    printf("\n");
    printf("  Available Size:         ");
    print_human_readable(avail_bytes, "");
    printf("\n");
    printf("\n");
}

int main(int argc, char *argv[]) {
    struct statfs sfs;
    struct statvfs svfs;
    int fd;

    printf("--- Demonstrating statfs vs statvfs ---\n");

    // 1. 如果没有提供命令行参数,则查询根目录 "/"
    const char *path = "/";
    if (argc > 1) {
        path = argv[1];
    }
    printf("Querying path: '%s'\n\n", path);

    // 2. 使用 statfs 查询
    if (statfs(path, &sfs) == 0) {
        print_fs_info_statfs(path, &sfs);
    } else {
        printf("statfs failed for '%s': %s\n\n", path, strerror(errno));
    }

    // 3. 使用 statvfs 查询
    if (statvfs(path, &svfs) == 0) {
        print_fs_info_statvfs(path, &svfs);
    } else {
        printf("statvfs failed for '%s': %s\n\n", path, strerror(errno));
    }

    // 4. 演示 fstatfs 和 fstatvfs (通过文件描述符)
    printf("=== Comparing fstatfs and fstatvfs ===\n");
    fd = open(path, O_RDONLY); // 尝试打开路径 (对于目录是合法的)
    if (fd != -1) {
        printf("Opened '%s' as file descriptor %d\n", path, fd);

        if (fstatfs(fd, &sfs) == 0) {
            printf("\n--- fstatfs via fd %d ---\n", fd);
            printf("  Total Fragments (fstatfs): %ld\n", (long)sfs.f_blocks);
            printf("  Free Fragments (fstatfs):  %ld\n", (long)sfs.f_bfree);
        } else {
            printf("fstatfs failed: %s\n", strerror(errno));
        }

        if (fstatvfs(fd, &svfs) == 0) {
            printf("\n--- fstatvfs via fd %d ---\n", fd);
            printf("  Total Fragments (fstatvfs): %ld\n", (long)svfs.f_blocks);
            printf("  Free Fragments (fstatvfs):  %ld\n", (long)svfs.f_bfree);
        } else {
            printf("fstatvfs failed: %s\n", strerror(errno));
        }

        close(fd);
    } else {
        // 如果打开失败(例如 path 是文件),尝试打开文件本身
        fd = open(path, O_RDONLY);
        if (fd != -1) {
             printf("Opened file '%s' as file descriptor %d\n", path, fd);
             // ... (对文件进行 fstatfs/fstatvfs 调用)
             // 为简洁起见,此处省略对文件的详细查询,逻辑同上
             close(fd);
        } else {
            printf("Could not open '%s' as file/dir for fstatfs/fstatvfs demo.\n", path);
        }
    }

    // 5. 演示一个实际应用:检查磁盘空间是否充足
    printf("\n=== Checking Disk Space ===\n");
    const char *check_path = "/tmp"; // 检查 /tmp 分区
    if (statvfs(check_path, &svfs) == 0) {
        double avail_bytes = (double)svfs.f_bavail * svfs.f_frsize;
        double required_bytes = 100 * 1024 * 1024; // 假设需要 100MB

        printf("Checking if '%s' has at least %.2f MiB available...\n",
               check_path, required_bytes / (1024.0*1024.0));

        if (avail_bytes >= required_bytes) {
            printf("OK: ");
            print_human_readable(avail_bytes, "");
            printf(" available, %.2f MiB required.\n", required_bytes / (1024.0*1024.0));
        } else {
            printf("WARNING: Only ");
            print_human_readable(avail_bytes, "");
            printf(" available, %.2f MiB required. Insufficient space!\n", required_bytes / (1024.0*1024.0));
        }
    } else {
        perror("statvfs for space check");
    }

    return 0;
}

10. 编译和运行

# 假设代码保存在 statfs_statvfs_example.c 中
gcc -o statfs_statvfs_example statfs_statvfs_example.c

# 1. 不带参数运行,查询根目录 "/"
./statfs_statvfs_example

# 2. 带参数运行,查询指定路径
./statfs_statvfs_example /home
./statfs_statvfs_example /tmp

11. 预期输出 (片段)

--- Demonstrating statfs vs statvfs ---
Querying path: '/'

=== Filesystem information for '/' (via statfs) ===
  Optimal I/O Block Size: 4096 bytes
  Fragment Size:          4096 bytes
  Total Fragments:        123456789
  Free Fragments:         56789012
  Available Fragments:    50000000 (for non-root users)
  Total Inodes:           30000000
  Free Inodes:            25000000
  Filesystem ID:          12345:67890
  Maximum Filename Length: 255
  Mount Flags:            0x400
  Total Size:             471.86 GiB
  Available Size:         190.73 GiB
  Filesystem Type:        ext2/ext3/ext4

=== Filesystem information for '/' (via statvfs) ===
  Filesystem Block Size:  4096 bytes
  Fragment Size:          4096 bytes
  Total Fragments:        123456789
  Free Fragments:         56789012
  Available Fragments:    50000000 (for non-root users)
  Available Fragments (non-root): 50000000
  Total Inodes:           30000000
  Free Inodes:            25000000
  Available Inodes (non-root): 25000000
  Filesystem ID:          12345
  Maximum Filename Length: 255
  Mount Flags:            0x400
  Total Size:             471.86 GiB
  Available Size:         190.73 GiB

=== Comparing fstatfs and fstatvfs ===
Opened '/' as file descriptor 3

--- fstatfs via fd 3 ---
  Total Fragments (fstatfs): 123456789
  Free Fragments (fstatfs):  56789012

--- fstatvfs via fd 3 ---
  Total Fragments (fstatvfs): 123456789
  Free Fragments (fstatvfs):  56789012

=== Checking Disk Space ===
Checking if '/tmp' has at least 100.00 MiB available...
OK: 7.81 GiB available, 100.00 MiB required.

12. 总结

statfs 和 statvfs 都是用于获取文件系统信息的强大工具。

  • statfs: Linux 特定的系统调用,可能提供更详细的 Linux 专有信息(如文件系统类型魔数)。
  • statvfs: POSIX 标准系统调用,具有更好的可移植性,是跨平台开发的首选。
  • 共同点:都能获取文件系统的总容量、可用空间、Inode 信息等关键统计数据。
  • 选择建议
    • 默认选择 statvfs,以确保代码的可移植性。
    • 仅当需要 Linux 特定信息且确定只在 Linux 上运行时,才考虑使用 statfs
  • 实际应用:常用于磁盘空间监控、系统信息工具(如 df 命令就是基于它们实现的)、资源管理等场景。理解返回结构体中各个字段的含义是正确使用它们的关键。
此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注