好的,我们继续按照您的要求学习 Linux 系统编程中的重要函数。这次我们介绍 statx
。
1. 函数介绍
statx
是一个相对较新的 Linux 系统调用(内核版本 >= 4.11),它是对传统 stat
, lstat
, fstat
系列函数的现代化扩展和增强。
它的主要功能是获取文件的状态信息(如大小、权限、所有者、时间戳等),与 stat
系列函数相同。但 statx
提供了以下显著优势:
- 更高效: 通过
flags
参数,调用者可以精确指定需要查询哪些文件属性,内核只返回请求的信息,避免了获取不必要的数据,从而提高了效率。 - 更丰富的信息:
statx
可以返回一些传统stat
结构 (struct stat
) 无法提供的新属性,例如:- 创建时间 (
stx_btime
): 文件的创建时间(birth time),这是许多文件系统支持但传统stat
无法获取的。 - 扩展的文件类型和权限: 提供了更详细的文件类型和权限信息。
- 文件系统 ID (
stx_dev_major
,stx_dev_minor
): 更明确地标识文件所在的设备。
- 创建时间 (
- 更好的可扩展性:
statx
使用了新的struct statx
结构,这个结构设计时就考虑了未来的扩展性,更容易添加新字段而不会破坏现有程序。 - 统一接口: 一个函数就能实现
stat
,lstat
,fstat
的功能,通过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
参数,调用者可以指定只查询感兴趣的文件属性子集,提高效率。 - 灵活路径解析: 通过
dirfd
,pathname
,flags
的组合,可以实现相对路径查找、绝对路径查找、控制符号链接行为等多种路径解析方式。
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
代码解释:
- 定义了一个
print_statx_info
函数来格式化并打印struct statx
的内容。 - 在
main
函数中,首先调用statx(AT_FDCWD, pathname, 0, STATX_BASIC_STATS, &statx_buf)
。AT_FDCWD
: 使用当前工作目录解析相对路径。0
: 默认 flags,表示如果pathname
是符号链接,则跟随它。STATX_BASIC_STATS
: 请求所有基本的文件状态信息。&statx_buf
: 指向用于接收结果的struct statx
变量。
- 调用成功后,打印所有获取到的信息。
- 为了对比,调用传统的
stat()
函数获取相同文件的信息。 - 再次调用
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
代码解释:
- 创建一个目标文件
target_file.txt
和一个指向它的符号链接my_symlink_to_target
。 - 比较
flags
:- 调用
statx(symlink_name, 0, ...)
:默认行为,跟随符号链接。返回的是目标文件的信息(inode 是目标文件的)。 - 调用
statx(symlink_name, AT_SYMLINK_NOFOLLOW, ...)
:不跟随符号链接。返回的是符号链接本身的信息(inode 是符号链接的)。
- 调用
- 获取创建时间:
- 调用
statx(target_file, 0, STATX_BTIME, ...)
尝试获取目标文件的创建时间。 - 检查返回的
statx_buf.stx_mask
是否包含STATX_BTIME
。如果包含,说明文件系统支持并返回了创建时间;否则,说明不支持。
- 调用
- 使用
AT_EMPTY_PATH
:- 首先打开目标文件得到文件描述符
fd
。 - 调用
statx(fd, "", AT_EMPTY_PATH, ...)
。这里pathname
是空字符串,dirfd
是fd
,AT_EMPTY_PATH
标志告诉statx
查询fd
本身引用的文件。 - 这提供了一种通过文件描述符获取文件状态的方法,类似于
fstat
,但具有statx
的所有优势。
- 首先打开目标文件得到文件描述符
重要提示与注意事项:
- 内核版本:
statx
需要 Linux 内核 4.11 或更高版本。 - glibc 版本: 需要 glibc 2.28 或更高版本才能在
<sys/stat.h>
中提供statx
函数声明。如果 glibc 版本较低,可能需要手动定义或使用syscall
。 - 效率: 通过使用
mask
参数,只请求需要的字段,可以显著提高性能,尤其是在网络文件系统或需要频繁查询的场景下。 stx_btime
(创建时间): 这个字段的可用性高度依赖于底层文件系统。例如,ext4 在较新内核上可能支持,而 tmpfs 或某些网络文件系统可能不支持。务必检查stx_mask
来确认字段是否有效。dirfd
和AT_*
标志: 这些参数提供了强大的路径解析能力,特别是AT_SYMLINK_NOFOLLOW
和AT_EMPTY_PATH
。- 错误处理: 始终检查返回值和
errno
。ENOENT
(文件不存在)、EACCES
(权限不足) 是常见的错误。 - 替代方案: 对于不支持
statx
的旧系统,必须回退到使用stat
,lstat
,fstat
。
总结:
statx
是 Linux 文件状态查询功能的一次重要升级。它通过引入更精细的查询控制 (mask
)、更丰富的属性(如 btime
)、更灵活的路径解析 (dirfd
, flags
) 以及更好的可扩展性 (struct statx
),为开发者提供了更强大、更高效的文件元数据获取能力。对于追求性能和需要访问现代文件系统特性的应用程序来说,statx
是首选的文件状态查询接口。