statx系统调用及示例

好的,我们继续按照您的要求学习 Linux 系统编程中的重要函数。这次我们介绍 statx


1. 函数介绍

statx 是一个相对较新的 Linux 系统调用(内核版本 >= 4.11),它是对传统 statlstatfstat 系列函数的现代化扩展和增强

它的主要功能是获取文件的状态信息(如大小、权限、所有者、时间戳等),与 stat 系列函数相同。但 statx 提供了以下显著优势:

  1. 更高效: 通过 flags 参数,调用者可以精确指定需要查询哪些文件属性,内核只返回请求的信息,避免了获取不必要的数据,从而提高了效率。
  2. 更丰富的信息statx 可以返回一些传统 stat 结构 (struct stat) 无法提供的新属性,例如:
    • 创建时间 (stx_btime): 文件的创建时间(birth time),这是许多文件系统支持但传统 stat 无法获取的。
    • 扩展的文件类型和权限: 提供了更详细的文件类型和权限信息。
    • 文件系统 ID (stx_dev_majorstx_dev_minor): 更明确地标识文件所在的设备。
  3. 更好的可扩展性statx 使用了新的 struct statx 结构,这个结构设计时就考虑了未来的扩展性,更容易添加新字段而不会破坏现有程序。
  4. 统一接口: 一个函数就能实现 statlstatfstat 的功能,通过 flags 参数控制是否跟随符号链接。

简单来说,statx 是一个更快、更强大、更灵活的 stat


2. 函数原型

#include <fcntl.h>     // 定义 AT_* 常量和 AT_STATX_* 常量
#include <sys/stat.h>  // 定义 struct statx

int statx(int dirfd, const char *pathname, int flags,
          unsigned int mask, struct statx *statxbuf);

3. 功能

  • 获取文件状态: 根据提供的 pathname(结合 dirfd 和 flags)获取指定文件的详细状态信息。
  • 按需查询: 通过 mask 参数,调用者可以指定只查询感兴趣的文件属性子集,提高效率。
  • 灵活路径解析: 通过 dirfdpathnameflags 的组合,可以实现相对路径查找、绝对路径查找、控制符号链接行为等多种路径解析方式。

4. 参数

  • int dirfd: 用作相对路径查找的起始目录文件描述符
    • 如果 pathname 是相对路径(例如 "subdir/file.txt"),则相对于 dirfd 指向的目录进行查找。
    • 如果 pathname 是绝对路径(例如 "/home/user/file.txt"),则 dirfd 被忽略。
    • 可以传入特殊的值 AT_FDCWD,表示使用当前工作目录作为起始点进行相对路径查找。
  • const char *pathname: 指向要查询状态的文件的路径名。可以是相对路径或绝对路径。
  • int flags: 控制路径解析行为的标志位。可以是以下值的按位或组合:
    • 0: 默认行为。
    • AT_SYMLINK_NOFOLLOW: 如果 pathname 是一个符号链接,则不跟随该链接,而是返回符号链接本身的属性(类似 lstat 的行为)。
    • AT_NO_AUTOMOUNT: 阻止在路径解析过程中触发自动挂载文件系统。
    • AT_EMPTY_PATH: 如果 pathname 是一个空字符串 (""),则查询 dirfd 本身所引用的文件的状态。dirfd 必须是一个有效的文件描述符。
    • AT_STATX_SYNC_AS_STAT: 使 statx 的同步语义与 stat 相同(默认行为)。
    • AT_STATX_FORCE_SYNC: 强制与服务器同步,获取最新的属性(例如,对于网络文件系统)。
    • AT_STATX_DONT_SYNC: 不要与服务器同步,使用缓存中的属性(如果可用)。
  • unsigned int mask: 这是一个位掩码,用于指定调用者感兴趣的文件属性。内核只会填充 struct statx 中与 mask 对应的字段。常用的掩码标志包括:
    • STATX_TYPE: 文件类型 (e.g., 普通文件, 目录, 符号链接)。
    • STATX_MODE: 文件权限和类型。
    • STATX_NLINK: 硬链接数。
    • STATX_UID: 所有者用户 ID。
    • STATX_GID: 所有者组 ID。
    • STATX_ATIME: 上次访问时间。
    • STATX_MTIME: 上次修改时间。
    • STATX_CTIME: 上次状态更改时间。
    • STATX_INO: inode 编号。
    • STATX_SIZE: 文件大小 (字节)。
    • STATX_BLOCKS: 分配的 512B 块数。
    • STATX_BASIC_STATS: 以上所有基本属性的组合。
    • STATX_BTIME: 文件创建时间(birth time)。
    • STATX_MNT_ID: (Linux 5.8+) 挂载 ID。
    • STATX_DIOALIGN: (Linux 6.1+) 直接 I/O 对齐要求。
    • STATX_ALL: 所有已知属性的组合。
  • struct statx *statxbuf: 指向一个 struct statx 类型的结构体的指针。函数调用成功后,该结构体将被填入文件的状态信息。

5. struct statx 结构体

statx 使用新的 struct statx 结构体来返回信息,比 struct stat 更丰富和可扩展。

struct statx {
    __u32 stx_mask;        // 哪些字段被填充了 (对应 mask)
    __u32 stx_blksize;     // 文件系统 I/O 块大小
    __u64 stx_attributes;   // 文件的额外属性 (如不可变、追加)
    __u32 stx_nlink;       // 硬链接数
    __u32 stx_uid;         // 所有者用户 ID
    __u32 stx_gid;         // 所有者组 ID
    __u16 stx_mode;        // 文件类型和权限
    __u16 stx_pad1;        // 填充
    __u64 stx_ino;         // inode 编号
    __u64 stx_size;        // 文件大小 (字节)
    __u64 stx_blocks;      // 分配的 512B 块数
    __u64 stx_attributes_mask; // 有效 attributes 位掩码
    struct statx_timestamp stx_atime; // 上次访问时间
    struct statx_timestamp stx_btime; // 创建时间 (birth time)
    struct statx_timestamp stx_ctime; // 上次状态更改时间
    struct statx_timestamp stx_mtime; // 上次修改时间
    __u32 stx_rdev_major;  // (如果是设备文件) 设备 ID 主号
    __u32 stx_rdev_minor;  // (如果是设备文件) 设备 ID 次号
    __u32 stx_dev_major;   // 文件所在设备 ID 主号
    __u32 stx_dev_minor;   // 文件所在设备 ID 次号
    __u64 stx_mnt_id;      // 挂载 ID (Linux 5.8+)
    __u32 stx_dio_mem_align; // 直接 I/O 内存对齐 (Linux 6.1+)
    __u32 stx_dio_offset_align; // 直接 I/O 偏移对齐 (Linux 6.1+)
    __u64 stx_subvol;      // 子卷 ID (Btrfs) (Linux 6.9+)
    __u64 stx_dax;         // DAX 支持 (Linux 6.10+)
    __u64 stx_pad2[9];     // 保留供将来扩展
};

struct statx_timestamp {
    __s64 tv_sec;          // 秒
    __u32 tv_nsec;         // 纳秒
    __s32 __reserved;
};

6. 返回值

  • 成功时: 返回 0。同时,statxbuf 指向的 struct statx 结构体被成功填充。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 ENOENT 文件不存在,EACCES 权限不足,EINVAL 参数无效等)。

7. 示例代码

示例 1:基本使用 statx 获取文件信息

这个例子演示了如何使用 statx 获取文件的基本信息,并与传统 stat 进行比较。

// statx_basic_example.c
#define _GNU_SOURCE // For statx
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <string.h>

void print_statx_info(const char *pathname, struct statx *sbx) {
    printf("--- statx info for '%s' ---\n", pathname);
    printf("  Mask (fields filled): 0x%x\n", sbx->stx_mask);
    printf("  Inode: %llu\n", (unsigned long long)sbx->stx_ino);
    printf("  Size: %llu bytes\n", (unsigned long long)sbx->stx_size);
    printf("  Blocks: %llu (512B blocks)\n", (unsigned long long)sbx->stx_blocks);
    printf("  Device ID: %xh/%d (major), %xh/%d (minor)\n",
           sbx->stx_dev_major, sbx->stx_dev_major,
           sbx->stx_dev_minor, sbx->stx_dev_minor);
    printf("  Links: %u\n", sbx->stx_nlink);
    printf("  Mode: %o (octal)\n", sbx->stx_mode);
    printf("  UID: %u\n", sbx->stx_uid);
    printf("  GID: %u\n", sbx->stx_gid);

    // 检查并打印时间戳
    if (sbx->stx_mask & STATX_ATIME) {
        char time_buf[100];
        struct tm *tm_info = localtime((time_t*)&sbx->stx_atime.tv_sec);
        strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
        printf("  Last Access: %s.%09ld\n", time_buf, sbx->stx_atime.tv_nsec);
    }
    if (sbx->stx_mask & STATX_MTIME) {
        char time_buf[100];
        struct tm *tm_info = localtime((time_t*)&sbx->stx_mtime.tv_sec);
        strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
        printf("  Last Modify: %s.%09ld\n", time_buf, sbx->stx_mtime.tv_nsec);
    }
    if (sbx->stx_mask & STATX_CTIME) {
        char time_buf[100];
        struct tm *tm_info = localtime((time_t*)&sbx->stx_ctime.tv_sec);
        strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
        printf("  Last Status Change: %s.%09ld\n", time_buf, sbx->stx_ctime.tv_nsec);
    }
    if (sbx->stx_mask & STATX_BTIME) {
        char time_buf[100];
        struct tm *tm_info = localtime((time_t*)&sbx->stx_btime.tv_sec);
        strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
        printf("  Birth Time: %s.%09ld\n", time_buf, sbx->stx_btime.tv_nsec);
    } else {
        printf("  Birth Time: Not available\n");
    }
    printf("---------------------------\n");
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    const char *pathname = argv[1];
    struct statx statx_buf;

    // --- 使用 statx 获取基本信息 ---
    // dirfd = AT_FDCWD: 使用当前目录作为相对路径起点
    // pathname = argv[1]: 文件路径
    // flags = 0: 默认行为,跟随符号链接
    // mask = STATX_BASIC_STATS: 请求所有基本文件属性
    // statx_buf: 用于接收结果的缓冲区
    printf("Calling statx with STATX_BASIC_STATS...\n");
    if (statx(AT_FDCWD, pathname, 0, STATX_BASIC_STATS, &statx_buf) == -1) {
        perror("statx");
        exit(EXIT_FAILURE);
    }

    print_statx_info(pathname, &statx_buf);

    // --- 比较: 使用传统 stat ---
    printf("\n--- Comparing with traditional stat ---\n");
    struct stat stat_buf;
    if (stat(pathname, &stat_buf) == -1) {
        perror("stat");
        // 即使 stat 失败,也继续演示 statx 的其他功能
    } else {
        printf("  stat() - Size: %ld bytes\n", (long)stat_buf.st_size);
        printf("  stat() - Inode: %ld\n", (long)stat_buf.st_ino);
        // ... 可以打印更多 stat 字段 ...
    }

    // --- 使用 statx 只获取特定信息 (例如,只获取大小和修改时间) ---
    printf("\nCalling statx with specific mask (SIZE | MTIME)...\n");
    struct statx statx_buf_minimal;
    if (statx(AT_FDCWD, pathname, 0, STATX_SIZE | STATX_MTIME, &statx_buf_minimal) == -1) {
        perror("statx minimal");
    } else {
        printf("  Minimal query result:\n");
        if (statx_buf_minimal.stx_mask & STATX_SIZE) {
            printf("    Size: %llu bytes\n", (unsigned long long)statx_buf_minimal.stx_size);
        }
        if (statx_buf_minimal.stx_mask & STATX_MTIME) {
            char time_buf[100];
            struct tm *tm_info = localtime((time_t*)&statx_buf_minimal.stx_mtime.tv_sec);
            strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
            printf("    Last Modify: %s.%09ld\n", time_buf, statx_buf_minimal.stx_mtime.tv_nsec);
        }
        printf("    Note: Only requested fields are filled. Mask = 0x%x\n", statx_buf_minimal.stx_mask);
    }

    return 0;
}

如何测试:

# 创建一个测试文件
echo "Hello, statx!" > test_statx_file.txt
touch -d "2023-01-01 10:00:00" test_statx_file.txt # 设置修改时间

# 编译并运行
gcc -o statx_basic_example statx_basic_example.c
./statx_basic_example test_statx_file.txt

代码解释:

  1. 定义了一个 print_statx_info 函数来格式化并打印 struct statx 的内容。
  2. 在 main 函数中,首先调用 statx(AT_FDCWD, pathname, 0, STATX_BASIC_STATS, &statx_buf)
    • AT_FDCWD: 使用当前工作目录解析相对路径。
    • 0: 默认 flags,表示如果 pathname 是符号链接,则跟随它。
    • STATX_BASIC_STATS: 请求所有基本的文件状态信息。
    • &statx_buf: 指向用于接收结果的 struct statx 变量。
  3. 调用成功后,打印所有获取到的信息。
  4. 为了对比,调用传统的 stat() 函数获取相同文件的信息。
  5. 再次调用 statx,但这次只请求 STATX_SIZE 和 STATX_MTIME
    • 这展示了 statx 的效率优势:内核只会填充请求的字段。
    • 打印结果时,可以看到 stx_mask 只包含 STATX_SIZE | STATX_MTIME 对应的位。

示例 2:使用 statx 处理符号链接和获取创建时间

这个例子演示了如何使用 statx 的 flags 参数来控制符号链接行为,并尝试获取文件的创建时间。

// statx_symlink_btime.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

int main() {
    const char *target_file = "target_file.txt";
    const char *symlink_name = "my_symlink_to_target";

    // 1. 创建目标文件
    FILE *f = fopen(target_file, "w");
    if (f) {
        fprintf(f, "This is the target file.\n");
        fclose(f);
        printf("Created target file: %s\n", target_file);
    } else {
        perror("Failed to create target file");
    }

    // 2. 创建符号链接
    if (symlink(target_file, symlink_name) == -1) {
        if (errno != EEXIST) { // EEXIST 表示链接已存在,可以接受
            perror("Failed to create symbolic link");
            unlink(target_file);
            exit(EXIT_FAILURE);
        } else {
            printf("Symbolic link '%s' already exists.\n", symlink_name);
        }
    } else {
        printf("Created symbolic link '%s' -> '%s'\n", symlink_name, target_file);
    }

    struct statx statx_buf;

    // --- 3a. 使用 statx 跟随符号链接 (默认行为) ---
    printf("\n--- statx('%s', 0) - Following symlink ---\n", symlink_name);
    if (statx(AT_FDCWD, symlink_name, 0, STATX_BASIC_STATS | STATX_BTIME, &statx_buf) == -1) {
        perror("statx following symlink");
    } else {
        printf("  Inode: %llu (This is the INODE of the TARGET file)\n", (unsigned long long)statx_buf.stx_ino);
        printf("  File Type: ");
        if ((statx_buf.stx_mode & S_IFMT) == S_IFREG) printf("Regular File\n");
        else if ((statx_buf.stx_mode & S_IFMT) == S_IFLNK) printf("Symbolic Link\n");
        else printf("Other\n");
        if (statx_buf.stx_mask & STATX_BTIME) {
             printf("  Birth Time available.\n");
        } else {
            printf("  Birth Time NOT available.\n");
        }
    }

    // --- 3b. 使用 statx 不跟随符号链接 ---
    printf("\n--- statx('%s', AT_SYMLINK_NOFOLLOW) - NOT Following symlink ---\n", symlink_name);
    if (statx(AT_FDCWD, symlink_name, AT_SYMLINK_NOFOLLOW, STATX_BASIC_STATS | STATX_BTIME, &statx_buf) == -1) {
        perror("statx NOT following symlink");
    } else {
        printf("  Inode: %llu (This is the INODE of the SYMBOLIC LINK itself)\n", (unsigned long long)statx_buf.stx_ino);
        printf("  File Type: ");
        if ((statx_buf.stx_mode & S_IFMT) == S_IFREG) printf("Regular File\n");
        else if ((statx_buf.stx_mode & S_IFMT) == S_IFLNK) printf("Symbolic Link\n");
        else printf("Other\n");
        if (statx_buf.stx_mask & STATX_BTIME) {
             printf("  Birth Time available.\n");
        } else {
            printf("  Birth Time NOT available.\n");
        }
    }

    // --- 4. 尝试获取创建时间 (Birth Time) ---
    printf("\n--- Attempting to get Birth Time (stx_btime) ---\n");
    // 需要确保内核和文件系统支持
    if (statx(AT_FDCWD, target_file, 0, STATX_BTIME, &statx_buf) == -1) {
        perror("statx for btime");
    } else {
        if (statx_buf.stx_mask & STATX_BTIME) {
            char time_buf[100];
            struct tm *tm_info = localtime((time_t*)&statx_buf.stx_btime.tv_sec);
            strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
            printf("  Birth Time of '%s': %s.%09ld\n", target_file, time_buf, statx_buf.stx_btime.tv_nsec);
        } else {
            printf("  Birth Time is NOT supported by the filesystem for '%s'.\n", target_file);
        }
    }

    // --- 5. 使用 AT_EMPTY_PATH 查询已打开文件的状态 ---
    printf("\n--- Using AT_EMPTY_PATH with an open file descriptor ---\n");
    int fd = open(target_file, O_RDONLY);
    if (fd == -1) {
        perror("open target file");
    } else {
        // pathname 为空字符串 "", dirfd 是有效的文件描述符
        if (statx(fd, "", AT_EMPTY_PATH, STATX_BASIC_STATS, &statx_buf) == -1) {
            perror("statx with AT_EMPTY_PATH");
        } else {
            printf("  Status of file descriptor %d (refers to '%s'):\n", fd, target_file);
            printf("    Size: %llu bytes\n", (unsigned long long)statx_buf.stx_size);
            printf("    Inode: %llu\n", (unsigned long long)statx_buf.stx_ino);
        }
        close(fd);
    }

    // 清理 (可选)
    // printf("\nCleaning up...\n");
    // unlink(symlink_name);
    // unlink(target_file);

    return 0;
}

如何测试:

gcc -o statx_symlink_btime statx_symlink_btime.c
./statx_symlink_btime

代码解释:

  1. 创建一个目标文件 target_file.txt 和一个指向它的符号链接 my_symlink_to_target
  2. 比较 flags:
    • 调用 statx(symlink_name, 0, ...):默认行为,跟随符号链接。返回的是目标文件的信息(inode 是目标文件的)。
    • 调用 statx(symlink_name, AT_SYMLINK_NOFOLLOW, ...):不跟随符号链接。返回的是符号链接本身的信息(inode 是符号链接的)。
  3. 获取创建时间:
    • 调用 statx(target_file, 0, STATX_BTIME, ...) 尝试获取目标文件的创建时间。
    • 检查返回的 statx_buf.stx_mask 是否包含 STATX_BTIME。如果包含,说明文件系统支持并返回了创建时间;否则,说明不支持。
  4. 使用 AT_EMPTY_PATH:
    • 首先打开目标文件得到文件描述符 fd
    • 调用 statx(fd, "", AT_EMPTY_PATH, ...)。这里 pathname 是空字符串,dirfd 是 fdAT_EMPTY_PATH 标志告诉 statx 查询 fd 本身引用的文件。
    • 这提供了一种通过文件描述符获取文件状态的方法,类似于 fstat,但具有 statx 的所有优势。

重要提示与注意事项:

  1. 内核版本statx 需要 Linux 内核 4.11 或更高版本。
  2. glibc 版本: 需要 glibc 2.28 或更高版本才能在 <sys/stat.h> 中提供 statx 函数声明。如果 glibc 版本较低,可能需要手动定义或使用 syscall
  3. 效率: 通过使用 mask 参数,只请求需要的字段,可以显著提高性能,尤其是在网络文件系统或需要频繁查询的场景下。
  4. stx_btime (创建时间): 这个字段的可用性高度依赖于底层文件系统。例如,ext4 在较新内核上可能支持,而 tmpfs 或某些网络文件系统可能不支持。务必检查 stx_mask 来确认字段是否有效。
  5. dirfd 和 AT_* 标志: 这些参数提供了强大的路径解析能力,特别是 AT_SYMLINK_NOFOLLOW 和 AT_EMPTY_PATH
  6. 错误处理: 始终检查返回值和 errnoENOENT (文件不存在)、EACCES (权限不足) 是常见的错误。
  7. 替代方案: 对于不支持 statx 的旧系统,必须回退到使用 statlstatfstat

总结:

statx 是 Linux 文件状态查询功能的一次重要升级。它通过引入更精细的查询控制 (mask)、更丰富的属性(如 btime)、更灵活的路径解析 (dirfdflags) 以及更好的可扩展性 (struct statx),为开发者提供了更强大、更高效的文件元数据获取能力。对于追求性能和需要访问现代文件系统特性的应用程序来说,statx 是首选的文件状态查询接口。

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

发表回复

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