statfs系统调用及示例

好的,我们来深入学习 statfs 系统调用

1. 函数介绍

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

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

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

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

典型应用场景

  • 磁盘空间监控:检查磁盘剩余空间,防止程序因磁盘写满而崩溃。
  • 系统信息工具:像 df 命令就是使用 statfs (或类似的 statvfs) 来显示磁盘使用情况的。
  • 文件系统类型检查:确认某个挂载点使用的是什么类型的文件系统(例如,检查 /tmp 是否是 tmpfs)。
  • 资源管理:根据可用空间决定是否执行某些操作。

2. 函数原型

#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); // fstatfs 通过已打开的文件描述符查询

3. 功能

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

4. 参数

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

5. 返回值

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

6. 错误码 (errno)

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

7. struct statfs 结构体

这个结构体包含了文件系统的各种信息。主要成员包括(定义可能因架构和内核版本略有不同):

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;   /* 文件系统挂载标志 */
    ...                   /* 可能还有其他字段 */
};

关键字段解释:

  • f_bsize: 这是文件系统推荐用于 I/O 操作的块大小。进行读写操作时使用这个大小通常效率最高。
  • f_blocks: 文件系统总共有多少个块。
  • f_bfree: 文件系统总共有多少个空闲块。
  • f_bavail: 对普通用户(非 root)来说,实际还可以使用的空闲块数量。有些文件系统会保留一部分空间给 root 用户,以防系统关键进程因磁盘满而无法运行。
  • f_files: 文件系统总共包含多少个 Inode(索引节点)。每个文件或目录都对应一个 Inode。
  • f_ffree: 文件系统中空闲的 Inode 数量。
  • f_type: 文件系统的类型,用一个魔数 (Magic Number) 表示。例如,EXT4_SUPER_MAGIC (0xEF53) 代表 ext4,TMPFS_MAGIC (0x01021994) 代表 tmpfs。可以通过比较这个值来判断文件系统类型。
  • f_namelen: 文件系统支持的文件名或目录名的最大长度。

8. 相似函数或关联函数

  • statvfs / fstatvfs: POSIX 标准定义的函数,功能与 statfs / fstatfs 几乎相同,但使用 struct statvfs 结构体。通常推荐使用 statvfs 以获得更好的可移植性。
  • df: 命令行工具,显示文件系统磁盘空间使用情况。它在底层调用的就是 statfs 或 statvfs
  • getmntent: 用于读取 /proc/mounts 或 /etc/mtab 文件,获取系统上所有已挂载文件系统的信息。
  • du: 命令行工具,估算文件和目录的空间使用情况。它通过遍历目录和文件来计算,而不是查询文件系统元数据。

9. 示例代码

下面的示例演示了如何使用 statfs 来查询不同路径的文件系统信息。

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

// 打印文件系统信息的辅助函数
void print_fs_info(const char* path, const struct statfs *sfs) {
    printf("Filesystem information for '%s':\n", path);
    printf("  Optimal I/O Block Size: %ld bytes\n", (long)sfs->f_bsize);
    printf("  Total Data Blocks:      %ld\n", (long)sfs->f_blocks);
    printf("  Free Blocks:            %ld\n", (long)sfs->f_bfree);
    printf("  Available Blocks:       %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("  Fragment Size:          %ld\n", (long)sfs->f_frsize);
    printf("  Mount Flags:            0x%lx\n", (unsigned long)sfs->f_flags);

    // 计算并打印人类可读的空间大小
    // 注意:块大小和数量可能非常大,使用 double 避免溢出
    double total_bytes = (double)sfs->f_blocks * sfs->f_bsize;
    double free_bytes = (double)sfs->f_bfree * sfs->f_bsize;
    double avail_bytes = (double)sfs->f_bavail * sfs->f_bsize;

    const char *units[] = {"B", "KiB", "MiB", "GiB", "TiB"};
    int unit_index = 0;
    double size_to_print = total_bytes;
    while (size_to_print >= 1024 && unit_index < 4) {
        size_to_print /= 1024;
        unit_index++;
    }
    printf("  Total Size:             %.2f %s\n", size_to_print, units[unit_index]);

    unit_index = 0;
    size_to_print = avail_bytes;
    while (size_to_print >= 1024 && unit_index < 4) {
        size_to_print /= 1024;
        unit_index++;
    }
    printf("  Available Size:         %.2f %s\n", size_to_print, units[unit_index]);

    // 识别文件系统类型 (仅列举几个常见类型)
    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");
}

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

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

    // 1. 如果没有提供命令行参数,则查询几个常见的路径
    if (argc == 1) {
        const char *default_paths[] = {"/", "/tmp", "/home", "/proc", "/sys", "/dev"};
        int num_paths = sizeof(default_paths) / sizeof(default_paths[0]);

        for (int i = 0; i < num_paths; i++) {
            if (statfs(default_paths[i], &sfs) == 0) {
                print_fs_info(default_paths[i], &sfs);
            } else {
                // 忽略某些可能不存在或无法访问的路径的错误
                if (errno != ENOENT && errno != EACCES) {
                     printf("Failed to statfs '%s': %s\n", default_paths[i], strerror(errno));
                     printf("\n");
                }
            }
        }
    } else {
        // 2. 如果提供了命令行参数,则查询指定的路径
        for (int i = 1; i < argc; i++) {
            const char *path = argv[i];
            printf("Querying path: '%s'\n", path);

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

            // 使用 fstatfs 查询 (如果路径是文件)
            // 先打开文件
            fd = open(path, O_RDONLY);
            if (fd != -1) {
                if (fstatfs(fd, &sfs) == 0) {
                    printf("Querying via file descriptor for '%s':\n", path);
                    print_fs_info("(via fd)", &sfs);
                } else {
                    printf("fstatfs failed for fd of '%s': %s\n", path, strerror(errno));
                }
                close(fd);
            } else {
                // 如果打开失败,可能是因为 path 是目录,跳过 fstatfs
                printf("Could not open '%s' as file, skipping fstatfs.\n", path);
            }
            printf("------------------------\n");
        }
    }

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

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

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

    return 0;
}

10. 编译和运行

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

# 1. 不带参数运行,查询默认路径
./statfs_example

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

# 3. 查询一个文件
touch /tmp/test_statfs_file
./statfs_example /tmp/test_statfs_file

11. 预期输出 (片段)

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

Filesystem information for '/tmp':
  Optimal I/O Block Size: 4096 bytes
  Total Data Blocks:      2048000
  Free Blocks:            2048000
  Available Blocks:       2048000 (for non-root users)
  Total Inodes:           512000
  Free Inodes:            512000
  Filesystem ID:          0:1234567
  Maximum Filename Length: 255
  Fragment Size:          4096
  Mount Flags:            0x41c
  Total Size:             7.81 GiB
  Available Size:         7.81 GiB
  Filesystem Type:        tmpfs

Filesystem information for '/proc':
  ...
  Filesystem Type:        proc
  ...

Filesystem information for '/sys':
  ...
  Filesystem Type:        sysfs
  ...

--- Checking Disk Space ---
Checking if '/tmp' has at least 100.00 MiB available...
OK: 8000.00 MiB available, 100.00 MiB required.

12. 总结

statfs 是一个非常实用的系统调用,用于获取文件系统级别的信息。它对于系统管理、磁盘监控和资源检查类的应用程序非常有价值。通过检查空闲块数 (f_bavail) 和块大小 (f_bsize),可以轻松计算出可用磁盘空间,这对于防止程序因磁盘写满而出错至关重要。理解 struct statfs 中各个字段的含义是使用此函数的关键。

此条目发表在linux文章分类目录,贴了标签。将固定链接加入收藏夹。

发表回复

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