fstatfs系统调用及示例

fstatfs 函数详解

  1. 函数介绍

fstatfs 是 Linux 系统中用于获取文件系统统计信息的系统调用。可以把这个函数想象成一个”文件系统体检工具”——当你给它一个已打开的文件描述符时,它会告诉你这个文件所在文件系统的详细信息,比如总空间多大、已用多少、还剩多少、支持什么特性等等。

就像你去体检时医生会检查你的身高、体重、血压等指标一样,fstatfs 会检查文件系统的”健康状况”和”基本信息”。

  1. 函数原型
1
2
3
4
#include <sys/vfs.h>    /* 或者 #include <sys/statfs.h> */

int fstatfs(int fd, struct statfs *buf);

  1. 功能

fstatfs 函数用于获取与指定文件描述符相关的文件系统的统计信息。它填充一个 statfs 结构体,其中包含了文件系统的各种参数和状态信息。

  1. 参数
  • fd: 已打开的文件描述符(可以是任何类型的文件:普通文件、目录、设备等)

  • buf: 指向 struct statfs 结构体的指针,用于存储返回的文件系统信息

  1. 返回值
  • 成功: 返回 0

  • 失败: 返回 -1,并设置相应的 errno 错误码

常见错误码:

  • EBADF: fd 不是有效的文件描述符

  • EFAULT: buf 指针无效

  • EIO: I/O 错误

  • ENOSYS: 系统不支持此操作

  1. 相似函数或关联函数
  • statfs: 通过文件路径获取文件系统信息(需要文件路径而非文件描述符)

  • statvfs: POSIX 标准的文件系统信息获取函数(提供更标准化的接口)

  • fstatvfs: 通过文件描述符获取 POSIX 标准的文件系统信息

  • stat: 获取文件本身的详细信息(不是文件系统信息)

  1. struct statfs 结构体详解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct statfs {
long f_type; /* 文件系统类型 */
long f_bsize; /* 文件系统块大小 */
long f_blocks; /* 总块数 */
long f_bfree; /* 空闲块数 */
long f_bavail; /* 对非超级用户可用的块数 */
long f_files; /* 总 inode 数 */
long f_ffree; /* 空闲 inode 数 */
fsid_t f_fsid; /* 文件系统 ID */
long f_namelen; /* 最大文件名长度 */
long f_frsize; /* 片段大小 */
long f_spare&#91;5]; /* 保留字段 */
};

  1. 示例代码

示例1:基础用法 - 查看文件系统空间信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/vfs.h>

int main() {
int fd;
struct statfs fs_info;
const char *filename = "/etc/passwd"; // 使用系统文件作为示例

// 打开文件
fd = open(filename, O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}

// 获取文件系统信息
if (fstatfs(fd, &fs_info) == -1) {
perror("fstatfs");
close(fd);
return 1;
}

// 显示文件系统信息
printf("=== 文件系统信息 ===\n");
printf("文件: %s\n", filename);
printf("文件系统类型: 0x%lx\n", fs_info.f_type);
printf("块大小: %ld 字节\n", fs_info.f_bsize);
printf("总块数: %ld\n", fs_info.f_blocks);
printf("空闲块数: %ld\n", fs_info.f_bfree);
printf("可用块数: %ld\n", fs_info.f_bavail);
printf("总 inode 数: %ld\n", fs_info.f_files);
printf("空闲 inode 数: %ld\n", fs_info.f_ffree);

// 计算空间使用情况(以 GB 为单位)
double total_space = (double)(fs_info.f_blocks * fs_info.f_bsize) / (1024 * 1024 * 1024);
double free_space = (double)(fs_info.f_bfree * fs_info.f_bsize) / (1024 * 1024 * 1024);
double avail_space = (double)(fs_info.f_bavail * fs_info.f_bsize) / (1024 * 1024 * 1024);

printf("\n=== 空间使用情况 ===\n");
printf("总空间: %.2f GB\n", total_space);
printf("空闲空间: %.2f GB\n", free_space);
printf("可用空间: %.2f GB\n", avail_space);
printf("使用率: %.2f%%\n",
(total_space - free_space) / total_space * 100);

close(fd);
return 0;
}

示例2:文件系统类型识别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/vfs.h>

// 常见文件系统类型的宏定义
#define EXT2_SUPER_MAGIC 0xEF53
#define EXT3_SUPER_MAGIC 0xEF53
#define EXT4_SUPER_MAGIC 0xEF53
#define XFS_SUPER_MAGIC 0x58465342
#define NTFS_SB_MAGIC 0x5346544E
#define TMPFS_MAGIC 0x01021994
#define PROC_SUPER_MAGIC 0x9FA0
#define SYSFS_MAGIC 0x62656572

// 根据文件系统类型返回字符串描述
const char* get_fs_type_name(long fs_type) {
switch (fs_type) {
case EXT2_SUPER_MAGIC:
return "ext2/ext3/ext4";
case XFS_SUPER_MAGIC:
return "XFS";
case NTFS_SB_MAGIC:
return "NTFS";
case TMPFS_MAGIC:
return "tmpfs";
case PROC_SUPER_MAGIC:
return "proc";
case SYSFS_MAGIC:
return "sysfs";
default:
return "未知文件系统";
}
}

int main() {
int fd;
struct statfs fs_info;
const char *test_files&#91;] = {"/etc/passwd", "/proc/version", "/sys/kernel"};
int num_files = sizeof(test_files) / sizeof(test_files&#91;0]);

for (int i = 0; i < num_files; i++) {
printf("\n=== 检查文件: %s ===\n", test_files&#91;i]);

// 打开文件
fd = open(test_files&#91;i], O_RDONLY);
if (fd == -1) {
perror("open");
printf("跳过此文件\n");
continue;
}

// 获取文件系统信息
if (fstatfs(fd, &fs_info) == -1) {
perror("fstatfs");
close(fd);
continue;
}

// 显示详细信息
printf("文件系统类型代码: 0x%lx\n", fs_info.f_type);
printf("文件系统类型名称: %s\n", get_fs_type_name(fs_info.f_type));
printf("最大文件名长度: %ld 字符\n", fs_info.f_namelen);
printf("片段大小: %ld 字节\n", fs_info.f_frsize);

close(fd);
}

return 0;
}

示例3:磁盘空间监控工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/vfs.h>
#include <string.h>

// 磁盘使用情况结构体
struct disk_usage {
char path&#91;256];
double total_gb;
double used_gb;
double free_gb;
double usage_percent;
};

// 获取磁盘使用情况
int get_disk_usage(const char *filepath, struct disk_usage *usage) {
int fd;
struct statfs fs_info;

// 打开文件
fd = open(filepath, O_RDONLY);
if (fd == -1) {
perror("open");
return -1;
}

// 获取文件系统信息
if (fstatfs(fd, &fs_info) == -1) {
perror("fstatfs");
close(fd);
return -1;
}

// 填充使用情况结构体
strncpy(usage->path, filepath, sizeof(usage->path) - 1);
usage->path&#91;sizeof(usage->path) - 1] = '\0';

// 计算空间大小(GB)
double block_size_gb = (double)fs_info.f_bsize / (1024 * 1024 * 1024);
usage->total_gb = fs_info.f_blocks * block_size_gb;
usage->free_gb = fs_info.f_bavail * block_size_gb; // 使用 f_bavail 而不是 f_bfree
usage->used_gb = usage->total_gb - usage->free_gb;
usage->usage_percent = (usage->used_gb / usage->total_gb) * 100;

close(fd);
return 0;
}

// 打印磁盘使用情况(带进度条效果)
void print_disk_usage(const struct disk_usage *usage) {
printf("路径: %s\n", usage->path);
printf("总空间: %.2f GB\n", usage->total_gb);
printf("已用空间: %.2f GB\n", usage->used_gb);
printf("可用空间: %.2f GB\n", usage->free_gb);

// 显示进度条
int bar_width = 50;
int filled_width = (int)((usage->usage_percent / 100) * bar_width);

printf("使用率: %.1f%% &#91;", usage->usage_percent);
for (int i = 0; i < bar_width; i++) {
if (i < filled_width) {
printf("#");
} else {
printf("-");
}
}
printf("]\n\n");
}

int main() {
// 测试不同的文件路径
const char *test_paths&#91;] = {
"/",
"/home",
"/tmp",
"/etc/passwd"
};

int num_paths = sizeof(test_paths) / sizeof(test_paths&#91;0]);

printf("=== 磁盘空间监控工具 ===\n\n");

for (int i = 0; i < num_paths; i++) {
struct disk_usage usage;

if (get_disk_usage(test_paths&#91;i], &usage) == 0) {
print_disk_usage(&usage);
} else {
printf("无法获取 %s 的磁盘信息\n\n", test_paths&#91;i]);
}
}

return 0;
}

编译和运行说明

1
2
3
gcc -o fstatfs_example fstatfs_example.c
./fstatfs_example

注意事项

文件系统类型: 不同的文件系统类型有不同的魔数(magic number),可以通过 f_type 字段识别

权限要求: 通常只需要对文件有读权限即可调用此函数

空间计算:

  • f_bfree: 所有用户都可用的空闲块数

  • f_bavail: 非超级用户可用的块数(通常更小)

移植性: statfs 结构体在不同系统上可能有差异,如需高移植性可考虑使用 statvfs

性能: 频繁调用此函数可能影响性能,建议缓存结果

这些示例展示了 fstatfs 函数的多种应用场景,从简单的空间信息获取到文件系统类型识别,再到实用的磁盘监控工具,帮助你全面理解这个函数的使用方法。

fstatfs系统调用及示例-CSDN博客

fstat系统调用及示例

fstat - 获取文件状态信息(通过文件描述符)

1. 函数介绍

fstat 是一个 Linux 系统调用,用于获取通过文件描述符指定的文件的详细状态信息。与 stat 不同,fstat 通过文件描述符而不是文件路径来操作文件,这样可以避免在多线程环境中因文件重命名或删除而导致的竞态条件。

fstat 返回的信息包括文件类型、大小、权限、所有者、时间戳等元数据,这些信息对于文件操作、权限检查和系统管理非常有用。

探索fstat系统调用,学习如何通过文件描述符获取文件状态信息。示例代码助您快速上手,深入理解Linux编程精髓。

2. 函数原型

1
2
3
4
5
6
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int fstat(int fd, struct stat *buf);

3. 功能

获取通过文件描述符指定的文件的状态信息,并将结果存储在 struct stat 结构体中。

4. 参数

  • int fd: 文件描述符,通过 open()、creat()、dup() 等函数获得

  • struct stat *buf: 指向 stat 结构体的指针,用于存储文件状态信息

5. stat 结构体定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct stat {
dev_t st_dev; /* 设备 ID(包含文件的设备)*/
ino_t st_ino; /* inode 号码 */
mode_t st_mode; /* 文件类型和权限 */
nlink_t st_nlink; /* 硬链接数 */
uid_t st_uid; /* 所有者用户 ID */
gid_t st_gid; /* 所有者组 ID */
dev_t st_rdev; /* 设备 ID(如果是特殊文件)*/
off_t st_size; /* 文件总大小(字节)*/
blksize_t st_blksize; /* 文件系统 I/O 块大小 */
blkcnt_t st_blocks; /* 分配的 512B 块数 */
time_t st_atime; /* 最后访问时间 */
time_t st_mtime; /* 最后修改时间 */
time_t st_ctime; /* 最后状态改变时间 */
};

6. 返回值

  • 成功时返回 0

  • 失败时返回 -1,并设置 errno

7. 常见 errno 错误码

  • EBADF: 无效的文件描述符

  • EFAULT: buf 指针指向无效地址

  • EOVERFLOW: 文件大小、inode 号或链接数超出范围

  • EIO: I/O 错误

  • EACCES: 权限不足(某些特殊情况下)

  • ENOMEM: 内存不足

8. 相似函数,或关联函数

  • stat(): 通过文件路径获取文件状态

  • lstat(): 获取符号链接本身的状态(不跟随链接)

  • fstatat(): 相对于目录文件描述符获取文件状态

  • statx(): 更现代的文件状态获取函数(Linux 4.11+)

  • access(): 检查文件访问权限

  • chmod(), chown(): 修改文件权限和所有者

9. 示例代码

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <errno.h>
#include <string.h>

void print_file_type(mode_t mode) {
if (S_ISREG(mode)) {
printf("文件类型: 普通文件\n");
} else if (S_ISDIR(mode)) {
printf("文件类型: 目录\n");
} else if (S_ISLNK(mode)) {
printf("文件类型: 符号链接\n");
} else if (S_ISCHR(mode)) {
printf("文件类型: 字符设备\n");
} else if (S_ISBLK(mode)) {
printf("文件类型: 块设备\n");
} else if (S_ISFIFO(mode)) {
printf("文件类型: FIFO/管道\n");
} else if (S_ISSOCK(mode)) {
printf("文件类型: 套接字\n");
} else {
printf("文件类型: 未知\n");
}
}

void print_file_permissions(mode_t mode) {
printf("权限: ");
printf((S_ISDIR(mode)) ? "d" : "-");
printf((mode & S_IRUSR) ? "r" : "-");
printf((mode & S_IWUSR) ? "w" : "-");
printf((mode & S_IXUSR) ? "x" : "-");
printf((mode & S_IRGRP) ? "r" : "-");
printf((mode & S_IWGRP) ? "w" : "-");
printf((mode & S_IXGRP) ? "x" : "-");
printf((mode & S_IROTH) ? "r" : "-");
printf((mode & S_IWOTH) ? "w" : "-");
printf((mode & S_IXOTH) ? "x" : "-");
printf("\n");
}

int main() {
int fd;
struct stat file_stat;
int ret;

// 创建测试文件
fd = open("test_file.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1) {
perror("创建文件失败");
exit(EXIT_FAILURE);
}

printf("成功创建文件,文件描述符: %d\n", fd);

// 写入一些内容
const char *content = "这是一个测试文件内容。\n包含多行文本。\n用于演示 fstat 功能。";
write(fd, content, strlen(content));

// 获取文件状态信息
ret = fstat(fd, &file_stat);
if (ret == -1) {
perror("获取文件状态失败");
close(fd);
exit(EXIT_FAILURE);
}

printf("\n=== 文件状态信息 ===\n");
printf("设备 ID: %ld\n", (long)file_stat.st_dev);
printf("inode 号: %ld\n", (long)file_stat.st_ino);

print_file_type(file_stat.st_mode);
print_file_permissions(file_stat.st_mode);

printf("硬链接数: %ld\n", (long)file_stat.st_nlink);
printf("所有者 UID: %d\n", file_stat.st_uid);
printf("所属组 GID: %d\n", file_stat.st_gid);
printf("文件大小: %ld 字节\n", (long)file_stat.st_size);
printf("I/O 块大小: %ld\n", (long)file_stat.st_blksize);
printf("分配的块数: %ld (512字节块)\n", (long)file_stat.st_blocks);

// 打印时间信息
printf("\n时间信息:\n");
printf("最后访问时间: %s", ctime(&file_stat.st_atime));
printf("最后修改时间: %s", ctime(&file_stat.st_mtime));
printf("状态改变时间: %s", ctime(&file_stat.st_ctime));

close(fd);
return 0;
}

示例2:错误处理和特殊文件类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>

void check_file_status(int fd, const char *description) {
struct stat file_stat;
int ret;

printf("\n检查 %s:\n", description);
printf("文件描述符: %d\n", fd);

ret = fstat(fd, &file_stat);
if (ret == -1) {
printf(" fstat 调用失败: %s\n", strerror(errno));
return;
}

printf(" 获取状态成功\n");
printf(" 文件大小: %ld 字节\n", (long)file_stat.st_size);

// 检查文件类型
if (S_ISREG(file_stat.st_mode)) {
printf(" 类型: 普通文件\n");
} else if (S_ISDIR(file_stat.st_mode)) {
printf(" 类型: 目录\n");
} else if (S_ISCHR(file_stat.st_mode)) {
printf(" 类型: 字符设备\n");
printf(" 设备号: %ld\n", (long)file_stat.st_rdev);
} else if (S_ISBLK(file_stat.st_mode)) {
printf(" 类型: 块设备\n");
printf(" 设备号: %ld\n", (long)file_stat.st_rdev);
} else if (S_ISFIFO(file_stat.st_mode)) {
printf(" 类型: FIFO/管道\n");
} else if (S_ISSOCK(file_stat.st_mode)) {
printf(" 类型: 套接字\n");
}
}

int main() {
int fd_regular, fd_dir, fd_stdin;

// 测试普通文件
fd_regular = open("test_file.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd_regular != -1) {
write(fd_regular, "test content", 12);
check_file_status(fd_regular, "普通文件");
close(fd_regular);
}

// 测试目录
fd_dir = open(".", O_RDONLY); // 打开当前目录
if (fd_dir != -1) {
check_file_status(fd_dir, "当前目录");
close(fd_dir);
}

// 测试标准输入(管道)
check_file_status(STDIN_FILENO, "标准输入");

// 测试无效文件描述符
printf("\n测试无效文件描述符:\n");
struct stat invalid_stat;
if (fstat(-1, &invalid_stat) == -1) {
printf(" 无效文件描述符测试: %s\n", strerror(errno));
}

return 0;
}

示例3:文件监控和变化检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <errno.h>
#include <string.h>

typedef struct {
time_t mtime; // 最后修改时间
off_t size; // 文件大小
} file_monitor_t;

int monitor_file_changes(int fd, file_monitor_t *last_state) {
struct stat current_stat;

if (fstat(fd, &current_stat) == -1) {
perror("获取文件状态失败");
return -1;
}

int changed = 0;

// 检查文件大小变化
if (current_stat.st_size != last_state->size) {
printf("文件大小变化: %ld -> %ld 字节\n",
(long)last_state->size, (long)current_stat.st_size);
changed = 1;
}

// 检查修改时间变化
if (current_stat.st_mtime != last_state->mtime) {
printf("文件修改时间变化\n");
printf(" 旧时间: %s", ctime(&last_state->mtime));
printf(" 新时间: %s", ctime(&current_stat.st_mtime));
changed = 1;
}

// 更新状态
last_state->mtime = current_stat.st_mtime;
last_state->size = current_stat.st_size;

return changed;
}

int main() {
int fd;
file_monitor_t monitor_state = {0, 0};
int i;

// 创建监控文件
fd = open("monitor_file.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1) {
perror("创建监控文件失败");
exit(EXIT_FAILURE);
}

printf("开始监控文件变化...\n");
printf("文件描述符: %d\n", fd);

// 初始化监控状态
if (monitor_file_changes(fd, &monitor_state) == -1) {
close(fd);
exit(EXIT_FAILURE);
}

printf("初始状态记录完成\n");

// 模拟文件变化
for (i = 0; i < 3; i++) {
sleep(2);

printf("\n--- 第 %d 次检查 ---\n", i + 1);

// 向文件添加内容
char buffer&#91;50];
snprintf(buffer, sizeof(buffer), "更新内容 %d\n", i + 1);
write(fd, buffer, strlen(buffer));

// 检查变化
if (monitor_file_changes(fd, &monitor_state)) {
printf("检测到文件变化\n");
} else {
printf("文件无变化\n");
}
}

close(fd);

// 清理测试文件
unlink("monitor_file.txt");

return 0;
}

10. 实用宏和函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 检查文件类型的宏
S_ISREG(m) // 是否为普通文件
S_ISDIR(m) // 是否为目录
S_ISCHR(m) // 是否为字符设备
S_ISBLK(m) // 是否为块设备
S_ISFIFO(m) // 是否为 FIFO
S_ISLNK(m) // 是否为符号链接
S_ISSOCK(m) // 是否为套接字

// 权限检查宏
S_IRUSR // 用户读权限
S_IWUSR // 用户写权限
S_IXUSR // 用户执行权限
S_IRGRP // 组读权限
S_IWGRP // 组写权限
S_IXGRP // 组执行权限
S_IROTH // 其他读权限
S_IWOTH // 其他写权限
S_IXOTH // 其他执行权限

11. 实际应用场景

fstat 在以下场景中非常有用:

  • 文件管理器显示文件详细信息

  • 备份工具检查文件是否需要备份

  • 编译系统检查源文件是否比目标文件新

  • 日志系统监控文件大小和修改时间

  • 安全工具验证文件属性

  • 系统监控工具跟踪文件系统变化

总结

fstat 是获取文件状态信息的核心系统调用,通过文件描述符提供了安全可靠的接口。关键要点:

使用文件描述符避免路径相关的竞态条件

返回丰富的文件元数据信息

支持各种文件类型的状态查询

正确处理各种错误情况

在文件监控和管理应用中广泛使用

fstat 与相关的 stat、lstat 函数一起,构成了 Linux 系统中文件状态查询的基础工具集。

fstat是Linux系统调用,用于通过文件描述符获取文件状态信息,避免了路径操作可能引发的竞态问题。该函数返回包括文件类型、大小、权限、时间戳等元数据,存储在stat结构体中。其函数原型为int fstat(int fd, struct stat *buf),成功返回0,失败返回-1并设置errno。常见错误包括无效描述符(EBADF)和内存不足(ENOMEM)。与stat通过路径获取信息不同,fstat更适用于多线程环境。示例代码展示了如何获取文件基本信息、类型判断和错误处理,适用于普通文件、目录等各类文件系统对象的状态检查。

https://www.calcguide.tech/2025/09/08/fstat系统调用及示例/

fsync系统调用及示例

fsync - 文件同步

函数介绍

fsync系统调用用于将文件的所有修改强制写入磁盘,确保数据的持久性。它会同步文件的数据和元数据(如修改时间、访问时间等),是保证数据安全的重要函数。掌握fsync系统调用,确保数据安全写入磁盘。了解其功能、用法及示例代码,提升文件操作效率与可靠性。关键词:fsync…

fsync系统调用, 文件同步fsync, Linux fsync使用示例, 如何使用fsync确保数据持久性, fsync函数详解, fsync与数据安全, 理解fsync及其重要性, 使用fsync提高文件操作安全性, fsync在编程中的应用, fsync系统调用实战案例

函数原型

1
2
3
4
#include <unistd.h>

int fsync(int fd);

功能

将文件描述符对应的文件所有修改(包括数据和元数据)强制写入磁盘。

参数

  • int fd: 文件描述符

返回值

  • 成功时返回0

失败时返回-1,并设置errno:

  • EBADF: 文件描述符无效

  • EIO: I/O错误

  • EINVAL: 文件描述符不支持同步

  • EROFS: 文件在只读文件系统上

相似函数

  • fdatasync(): 只同步文件数据,不同步所有元数据

  • sync(): 同步所有文件系统缓冲区

  • msync(): 同步内存映射文件

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>

int main() {
int fd;
struct stat stat_buf;
time_t write_time, sync_time;

printf("=== Fsync函数示例 ===\n");

// 示例1: 基本的文件同步操作
printf("\n示例1: 基本的文件同步操作\n");

// 创建测试文件
fd = open("test_fsync.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
exit(EXIT_FAILURE);
}
printf("成功创建测试文件,文件描述符: %d\n", fd);

// 写入数据
const char *data1 = "First line of data\n";
if (write(fd, data1, strlen(data1)) == -1) {
perror("写入第一行数据失败");
close(fd);
exit(EXIT_FAILURE);
}
printf("写入第一行数据: %s", data1);

// 获取写入时间
time(&write_time);
printf("写入时间: %s", ctime(&write_time));

// 同步文件到磁盘
printf("开始同步文件到磁盘...\n");
time(&sync_time);
if (fsync(fd) == -1) {
perror("文件同步失败");
} else {
printf("文件同步成功\n");
printf("同步完成时间: %s", ctime(&sync_time));
}

// 再写入更多数据
const char *data2 = "Second line of data\nThird line of data\n";
if (write(fd, data2, strlen(data2)) == -1) {
perror("写入更多数据失败");
} else {
printf("写入更多数据成功\n");
}

// 再次同步
if (fsync(fd) == -1) {
perror("第二次文件同步失败");
} else {
printf("第二次文件同步成功\n");
}

// 示例2: 同步前后文件状态对比
printf("\n示例2: 同步前后文件状态对比\n");

// 获取同步前的文件状态
if (fstat(fd, &stat_buf) == -1) {
perror("获取文件状态失败");
} else {
printf("同步前文件状态:\n");
printf(" 文件大小: %ld 字节\n", stat_buf.st_size);
printf(" 最后修改时间: %s", ctime(&stat_buf.st_mtime));
printf(" 最后访问时间: %s", ctime(&stat_buf.st_atime));
}

// 写入数据但不同步
const char *unsynced_data = "Unsynced data\n";
if (write(fd, unsynced_data, strlen(unsynced_data)) == -1) {
perror("写入未同步数据失败");
}

// 再次获取文件状态(可能还未写入磁盘)
if (fstat(fd, &stat_buf) == -1) {
perror("再次获取文件状态失败");
} else {
printf("写入未同步数据后的文件状态:\n");
printf(" 文件大小: %ld 字节\n", stat_buf.st_size);
}

// 同步文件
if (fsync(fd) == -1) {
perror("同步文件失败");
} else {
printf("文件同步完成\n");
}

// 同步后再次获取文件状态
if (fstat(fd, &stat_buf) == -1) {
perror("同步后获取文件状态失败");
} else {
printf("同步后文件状态:\n");
printf(" 文件大小: %ld 字节\n", stat_buf.st_size);
printf(" 最后修改时间: %s", ctime(&stat_buf.st_mtime));
}

// 示例3: 性能对比演示
printf("\n示例3: 性能对比演示\n");

// 测试不使用fsync的写入性能
clock_t start_time = clock();
for (int i = 0; i < 1000; i++) {
char buffer&#91;50];
sprintf(buffer, "Line %d: Performance test data\n", i);
if (write(fd, buffer, strlen(buffer)) == -1) {
perror("性能测试写入失败");
break;
}
}
clock_t without_sync_time = clock() - start_time;
printf("不使用fsync写入1000行数据耗时: %f 秒\n",
((double)without_sync_time) / CLOCKS_PER_SEC);

// 测试使用fsync的写入性能
start_time = clock();
for (int i = 1000; i < 2000; i++) {
char buffer&#91;50];
sprintf(buffer, "Line %d: Performance test data with sync\n", i);
if (write(fd, buffer, strlen(buffer)) == -1) {
perror("性能测试写入失败");
break;
}
if (fsync(fd) == -1) {
perror("同步失败");
break;
}
}
clock_t with_sync_time = clock() - start_time;
printf("使用fsync写入1000行数据耗时: %f 秒\n",
((double)with_sync_time) / CLOCKS_PER_SEC);

printf("注意: fsync会显著降低写入性能,但保证数据安全\n");

// 示例4: 错误处理演示
printf("\n示例4: 错误处理演示\n");

// 尝试对无效文件描述符同步
if (fsync(999) == -1) {
printf("对无效文件描述符同步: %s\n", strerror(errno));
}

// 尝试对只读文件同步(应该成功)
int readonly_fd = open("test_fsync.txt", O_RDONLY);
if (readonly_fd != -1) {
if (fsync(readonly_fd) == -1) {
printf("对只读文件同步: %s\n", strerror(errno));
} else {
printf("对只读文件同步成功\n");
}
close(readonly_fd);
}

// 示例5: 数据安全重要性演示
printf("\n示例5: 数据安全重要性演示\n");

// 创建重要的数据文件
int important_fd = open("important_data.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (important_fd != -1) {
printf("创建重要数据文件\n");

// 写入重要数据
const char *important_data = "Very important data that must not be lost\n";
if (write(important_fd, important_data, strlen(important_data)) != -1) {
printf("写入重要数据\n");

// 对于重要数据,必须同步到磁盘
if (fsync(important_fd) == -1) {
perror("重要数据同步失败");
} else {
printf("重要数据已安全同步到磁盘\n");
}
}

close(important_fd);
unlink("important_data.txt");
}

// 清理资源
printf("\n清理资源...\n");
if (close(fd) == -1) {
perror("关闭文件失败");
} else {
printf("成功关闭文件描述符 %d\n", fd);
}

if (unlink("test_fsync.txt") == -1) {
perror("删除测试文件失败");
} else {
printf("成功删除测试文件\n");
}

return 0;
}

fsync系统调用及示例-CSDN博客

https://www.calcguide.tech/2025/09/08/fsync系统调用及示例/

ftruncate系统调用及示例

ftruncate - 通过文件描述符截断文件

函数介绍

ftruncate系统调用用于将已打开的文件截断到指定长度。与truncate不同,ftruncate通过文件描述符而不是文件路径来操作文件,这在某些情况下更加高效和安全。

函数原型

1
2
3
4
5
#include <unistd.h>
#include <sys/types.h>

int ftruncate(int fd, off_t length);

功能

通过文件描述符将文件截断或扩展到指定长度。

ftruncate系统调用及示例-CSDN博客

参数

  • int fd: 文件描述符

  • off_t length: 目标文件长度(字节)

返回值

  • 成功时返回0

失败时返回-1,并设置errno:

  • EBADF: 文件描述符无效或不是为写入而打开

  • EFBIG: 长度参数过大

  • EINVAL: 文件描述符不支持截断

  • EIO: I/O错误

  • EPERM: 操作不被允许

  • EROFS: 文件在只读文件系统上

相似函数

  • truncate(): 通过文件路径截断文件

  • open()配合O_TRUNC标志

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>

int main() {
int fd;
struct stat stat_buf;

printf("=== Ftruncate函数示例 ===\n");

// 示例1: 基本的文件截断操作
printf("\n示例1: 基本的文件截断操作\n");

// 创建测试文件并写入数据
fd = open("test_ftruncate.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
exit(EXIT_FAILURE);
}

// 写入测试数据
const char *original_data =
"Line 1: This is the first line of data.\n"
"Line 2: This is the second line of data.\n"
"Line 3: This is the third line of data.\n"
"Line 4: This is the fourth line of data.\n"
"Line 5: This is the fifth line of data.\n";

if (write(fd, original_data, strlen(original_data)) == -1) {
perror("写入原始数据失败");
close(fd);
exit(EXIT_FAILURE);
}

printf("写入原始数据,长度: %ld 字节\n", (long)strlen(original_data));

// 获取原始文件状态
if (fstat(fd, &stat_buf) == -1) {
perror("获取文件状态失败");
} else {
printf("原始文件大小: %ld 字节\n", stat_buf.st_size);
}

// 使用ftruncate截断文件到80字节
printf("\n使用ftruncate将文件截断到80字节...\n");
if (ftruncate(fd, 80) == -1) {
perror("截断文件失败");
} else {
printf("文件截断成功\n");
}

// 检查截断后的文件状态
if (fstat(fd, &stat_buf) == -1) {
perror("获取截断后文件状态失败");
} else {
printf("截断后文件大小: %ld 字节\n", stat_buf.st_size);
}

// 读取截断后的数据验证
lseek(fd, 0, SEEK_SET); // 重置文件位置指针
char buffer&#91;200];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("截断后文件内容:\n%s", buffer);
}

// 示例2: 扩展文件
printf("\n示例2: 扩展文件\n");

// 使用ftruncate将文件扩展到150字节
printf("使用ftruncate将文件扩展到150字节...\n");
if (ftruncate(fd, 150) == -1) {
perror("扩展文件失败");
} else {
printf("文件扩展成功\n");
}

// 检查扩展后的文件状态
if (fstat(fd, &stat_buf) == -1) {
perror("获取扩展后文件状态失败");
} else {
printf("扩展后文件大小: %ld 字节\n", stat_buf.st_size);
}

// 读取扩展后的数据
lseek(fd, 0, SEEK_SET); // 重置文件位置指针
bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
printf("扩展后文件前%d字节内容:\n", (int)bytes_read);
// 显示前100个字符
for (int i = 0; i < bytes_read && i < 100; i++) {
if (buffer&#91;i] >= 32 && buffer&#91;i] <= 126) {
putchar(buffer&#91;i]);
} else if (buffer&#91;i] == '\n') {
putchar('\n');
} else {
printf("&#91;%02x]", (unsigned char)buffer&#91;i]);
}
}
printf("\n");
}

// 示例3: 截断到0字节(清空文件)
printf("\n示例3: 截断到0字节(清空文件)\n");

printf("将文件截断到0字节...\n");
if (ftruncate(fd, 0) == -1) {
perror("清空文件失败");
} else {
printf("文件清空成功\n");
}

if (fstat(fd, &stat_buf) == -1) {
perror("获取清空后文件状态失败");
} else {
printf("清空后文件大小: %ld 字节\n", stat_buf.st_size);
}

// 示例4: 与truncate的对比
printf("\n示例4: ftruncate与truncate对比\n");

// 重新写入数据
const char *compare_data = "Data for comparison test: ftruncate vs truncate";
if (write(fd, compare_data, strlen(compare_data)) == -1) {
perror("写入对比数据失败");
} else {
printf("写入对比数据: %s\n", compare_data);
}

// 使用ftruncate截断
clock_t ftruncate_start = clock();
if (ftruncate(fd, 20) == -1) {
perror("ftruncate失败");
} else {
clock_t ftruncate_time = clock() - ftruncate_start;
printf("ftruncate成功,耗时: %f 秒\n",
((double)ftruncate_time) / CLOCKS_PER_SEC);
}

// 重新创建文件测试truncate
close(fd);
unlink("test_ftruncate.txt");

fd = open("test_ftruncate.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd != -1) {
if (write(fd, compare_data, strlen(compare_data)) == -1) {
perror("重新写入数据失败");
}
close(fd);

// 使用truncate截断
clock_t truncate_start = clock();
if (truncate("test_ftruncate.txt", 20) == -1) {
perror("truncate失败");
} else {
clock_t truncate_time = clock() - truncate_start;
printf("truncate成功,耗时: %f 秒\n",
((double)truncate_time) / CLOCKS_PER_SEC);
}
}

// 重新打开文件继续演示
fd = open("test_ftruncate.txt", O_RDWR);
if (fd == -1) {
perror("重新打开文件失败");
exit(EXIT_FAILURE);
}

// 示例5: 不同长度的截断演示
printf("\n示例5: 不同长度的截断演示\n");

// 重新写入测试数据
const char *test_data = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
lseek(fd, 0, SEEK_SET);
if (ftruncate(fd, 0) == -1) { // 先清空
perror("清空文件失败");
}
if (write(fd, test_data, strlen(test_data)) == -1) {
perror("写入测试数据失败");
}
printf("重新写入测试数据: %s\n", test_data);

// 演示不同长度的截断
off_t lengths&#91;] = {10, 25, 40, 70};
for (int i = 0; i < 4; i++) {
printf("截断到%ld字节: ", lengths&#91;i]);
if (ftruncate(fd, lengths&#91;i]) == -1) {
printf("失败 - %s\n", strerror(errno));
} else {
if (fstat(fd, &stat_buf) == -1) {
printf("获取状态失败\n");
} else {
printf("成功,新大小: %ld字节\n", stat_buf.st_size);

// 读取并显示内容
lseek(fd, 0, SEEK_SET);
char read_buffer&#91;100];
ssize_t bytes_read = read(fd, read_buffer, stat_buf.st_size);
if (bytes_read > 0) {
read_buffer&#91;bytes_read] = '\0';
printf(" 内容: %s\n", read_buffer);
}
}
}
}

// 示例6: 错误处理演示
printf("\n示例6: 错误处理演示\n");

// 尝试对无效文件描述符操作
if (ftruncate(999, 100) == -1) {
printf("对无效文件描述符操作: %s\n", strerror(errno));
}

// 尝试对只读文件描述符操作
int readonly_fd = open("test_ftruncate.txt", O_RDONLY);
if (readonly_fd != -1) {
if (ftruncate(readonly_fd, 50) == -1) {
printf("对只读文件描述符操作: %s\n", strerror(errno));
}
close(readonly_fd);
}

// 尝试使用负数长度
if (ftruncate(fd, -10) == -1) {
printf("使用负数长度: %s\n", strerror(errno));
}

// 尝试对管道或套接字操作(需要特殊文件描述符)
int pipefd&#91;2];
if (pipe(pipefd) == 0) {
if (ftruncate(pipefd&#91;0], 100) == -1) {
printf("对管道操作: %s\n", strerror(errno));
}
close(pipefd&#91;0]);
close(pipefd&#91;1]);
}

// 示例7: 实际应用场景
printf("\n示例7: 实际应用场景\n");

// 场景1: 编辑器临时文件管理
printf("场景1: 编辑器临时文件管理\n");
int editor_fd = open("editor_temp.txt", O_CREAT | O_RDWR, 0644);
if (editor_fd != -1) {
// 模拟编辑器写入大量临时数据
char temp_data&#91;100];
for (int i = 0; i < 50; i++) {
sprintf(temp_data, "Temporary line %d for editor\n", i);
write(editor_fd, temp_data, strlen(temp_data));
}

if (fstat(editor_fd, &stat_buf) == 0) {
printf("临时文件大小: %ld 字节\n", stat_buf.st_size);

// 当用户撤销操作时,可能需要截断文件
off_t undo_position = stat_buf.st_size - 200; // 模拟撤销到某个位置
if (undo_position > 0) {
printf("撤销操作,截断到位置: %ld\n", undo_position);
if (ftruncate(editor_fd, undo_position) == -1) {
perror("撤销操作失败");
} else {
printf("撤销操作成功\n");
}
}
}

close(editor_fd);
unlink("editor_temp.txt");
}

// 场景2: 数据库文件维护
printf("场景2: 数据库文件维护\n");
int db_fd = open("database.dat", O_CREAT | O_RDWR, 0644);
if (db_fd != -1) {
// 模拟数据库文件增长
char db_record&#91;50];
for (int i = 0; i < 100; i++) {
sprintf(db_record, "Database record %d with some data\n", i);
write(db_fd, db_record, strlen(db_record));
}

if (fstat(db_fd, &stat_buf) == 0) {
printf("数据库文件原始大小: %ld 字节\n", stat_buf.st_size);

// 模拟数据库压缩操作
off_t compressed_size = stat_buf.st_size * 0.8; // 压缩到80%
printf("压缩数据库文件到: %ld 字节\n", compressed_size);
if (ftruncate(db_fd, compressed_size) == -1) {
perror("数据库压缩失败");
} else {
printf("数据库压缩成功\n");
}
}

close(db_fd);
unlink("database.dat");
}

// 场景3: 日志文件循环
printf("场景3: 日志文件循环\n");
int log_fd = open("circular_log.txt", O_CREAT | O_RDWR, 0644);
if (log_fd != -1) {
// 写入日志数据直到达到限制
const char *log_entry = "LOG: System operation completed successfully\n";
for (int i = 0; i < 20; i++) {
char entry&#91;100];
sprintf(entry, "Entry %d: %s", i, log_entry);
write(log_fd, entry, strlen(entry));
}

if (fstat(log_fd, &stat_buf) == 0) {
printf("日志文件当前大小: %ld 字节\n", stat_buf.st_size);

// 如果超过限制(比如1KB),保留最后512字节
if (stat_buf.st_size > 1024) {
printf("日志文件过大,保留最后512字节\n");

// 读取最后512字节
char last_data&#91;512];
off_t start_pos = stat_buf.st_size - 512;
if (start_pos > 0) {
lseek(log_fd, start_pos, SEEK_SET);
ssize_t bytes_read = read(log_fd, last_data, 512);
if (bytes_read > 0) {
// 截断文件到0,然后写入保留的数据
ftruncate(log_fd, 0);
lseek(log_fd, 0, SEEK_SET);
write(log_fd, last_data, bytes_read);
printf("日志循环完成,新大小: %ld 字节\n", (long)bytes_read);
}
}
}
}

close(log_fd);
unlink("circular_log.txt");
}

// 清理资源
printf("\n清理资源...\n");
if (close(fd) == -1) {
perror("关闭文件失败");
} else {
printf("成功关闭文件描述符 %d\n", fd);
}

if (unlink("test_ftruncate.txt") == -1) {
perror("删除测试文件失败");
} else {
printf("成功删除测试文件\n");
}

return 0;
}

futex系统调用及示例

futex 函数详解

  1. 函数介绍

futex(Fast Userspace muTEX)是 Linux 内核提供的一种高效的同步原语。可以把 futex 想象成一个”智能的红绿灯系统”——在大多数情况下,它在用户空间快速运行(就像绿灯畅通无阻),只有在发生竞争时才需要内核介入(就像遇到红灯需要交通警察协调)。

传统的互斥锁在用户空间和内核空间之间频繁切换,开销很大。而 futex 的巧妙之处在于:如果没有竞争,线程可以在用户空间直接完成同步操作;只有当出现等待情况时,才需要进入内核进行睡眠和唤醒操作。这就像排队买票,如果没人排队,你可以直接买票离开;如果人很多,就需要排队等待,这时工作人员会来维持秩序。

  1. 函数原型
1
2
3
4
5
6
7
8
#include <linux/futex.h>
#include <sys/syscall.h>
#include <unistd.h>

int futex(int *uaddr, int futex_op, int val,
const struct timespec *timeout, /* or: uint32_t val2 */
int *uaddr2, int val3);

注意:futex 是系统调用,通常通过 syscall() 函数调用。

  1. 功能

futex 提供了一种高效的用户空间同步机制,主要用于实现各种同步原语(如互斥锁、条件变量、信号量等)。它通过在用户空间和内核空间之间智能切换,实现了高性能的线程同步。

  1. 参数
  • uaddr: 指向用户空间的一个整型变量(futex 字),用于存储同步状态

  • futex_op: 操作类型(见下表)

  • val: 操作参数,根据不同的操作类型有不同的含义

  • timeout: 超时时间(可选,某些操作使用)

  • uaddr2: 第二个 futex 地址(某些操作使用)

  • val3: 第三个参数(某些操作使用)

  1. 常用操作类型

操作说明功能FUTEX_WAIT等待操作如果 *uaddr == val,则睡眠等待FUTEX_WAKE唤醒操作唤醒在 uaddr 上等待的 val 个线程FUTEX_WAIT_BITSET带位集的等待可以指定唤醒条件FUTEX_WAKE_BITSET带位集的唤醒按位集唤醒等待线程FUTEX_LOCK_PI优先级继承锁用于实现互斥锁FUTEX_UNLOCK_PI解锁优先级继承锁解除互斥锁

  1. 返回值

成功: 返回值根据操作类型而定

  • FUTEX_WAIT: 成功返回 0,超时返回 -1 并设置 errno 为 ETIMEDOUT

  • FUTEX_WAKE: 返回唤醒的线程数

失败: 返回 -1,并设置相应的 errno 错误码

常见错误码:

  • EAGAIN: 条件不满足(如 FUTEX_WAIT 时值已改变)

  • ETIMEDOUT: 等待超时

  • EINVAL: 参数无效

  • EFAULT: 地址无效

  1. 相似函数或关联函数
  • pthread_mutex_t: POSIX 互斥锁(基于 futex 实现)

  • pthread_cond_t: POSIX 条件变量(基于 futex 实现)

  • sem_t: POSIX 信号量(基于 futex 实现)

  • atomic operations: 原子操作(与 futex 配合使用)

  1. 示例代码

示例1:基础用法 - 实现简单的计数器同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/time.h>
#include <errno.h>
#include <stdatomic.h>

// futex 系统调用封装
int futex_wait(int *addr, int val, const struct timespec *timeout) {
return syscall(SYS_futex, addr, FUTEX_WAIT, val, timeout, NULL, 0);
}

int futex_wake(int *addr, int num) {
return syscall(SYS_futex, addr, FUTEX_WAKE, num, NULL, NULL, 0);
}

// 全局计数器和同步变量
atomic_int counter = 0;
int futex_var = 0; // 0: 可访问, 1: 正在访问

// 线程函数
void* worker_thread(void* arg) {
int thread_id = *(int*)arg;

for (int i = 0; i < 5; i++) {
// 尝试获取锁
int expected = 0;
while (!atomic_compare_exchange_weak(&futex_var, &expected, 1)) {
// 如果获取失败,等待
futex_wait(&futex_var, 1, NULL);
expected = 0;
}

// 临界区:访问共享资源
int old_value = atomic_fetch_add(&counter, 1);
printf("线程 %d: 计数器从 %d 增加到 %d\n",
thread_id, old_value, old_value + 1);

// 模拟一些工作
usleep(100000); // 100ms

// 释放锁
atomic_store(&futex_var, 0);
futex_wake(&futex_var, 1); // 唤醒一个等待的线程

// 非临界区工作
usleep(50000); // 50ms
}

return NULL;
}

int main() {
pthread_t threads&#91;3];
int thread_ids&#91;3] = {1, 2, 3};

printf("=== FUTEX 基础同步示例 ===\n");
printf("启动 3 个线程,每个线程对计数器执行 5 次操作\n\n");

// 创建线程
for (int i = 0; i < 3; i++) {
if (pthread_create(&threads&#91;i], NULL, worker_thread, &thread_ids&#91;i]) != 0) {
perror("pthread_create");
return 1;
}
}

// 等待所有线程完成
for (int i = 0; i < 3; i++) {
pthread_join(threads&#91;i], NULL);
}

printf("\n最终计数器值: %d\n", atomic_load(&counter));
return 0;
}

示例2:带超时的 futex 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/time.h>
#include <errno.h>
#include <stdatomic.h>

int futex_wait_timeout(int *addr, int val, int timeout_ms) {
struct timespec timeout;
timeout.tv_sec = timeout_ms / 1000;
timeout.tv_nsec = (timeout_ms % 1000) * 1000000;

return syscall(SYS_futex, addr, FUTEX_WAIT, val, &timeout, NULL, 0);
}

int futex_wake(int *addr, int num) {
return syscall(SYS_futex, addr, FUTEX_WAKE, num, NULL, NULL, 0);
}

// 生产者-消费者场景
int buffer = -1; // -1 表示空
int data_ready = 0; // 0: 无数据, 1: 有数据

void* producer_thread(void* arg) {
for (int i = 1; i <= 5; i++) {
printf("生产者: 准备生产数据 %d\n", i);

// 等待缓冲区为空
while (__atomic_load_n(&data_ready, __ATOMIC_ACQUIRE) == 1) {
printf("生产者: 缓冲区满,等待消费者...\n");
int ret = futex_wait_timeout(&data_ready, 1, 2000); // 2秒超时
if (ret == -1 && errno == ETIMEDOUT) {
printf("生产者: 等待超时,继续检查...\n");
}
}

// 生产数据
buffer = i;
__atomic_store_n(&data_ready, 1, __ATOMIC_RELEASE);
printf("生产者: 生产了数据 %d\n", i);

// 唤醒消费者
futex_wake(&data_ready, 1);

sleep(1);
}

return NULL;
}

void* consumer_thread(void* arg) {
for (int i = 0; i < 5; i++) {
printf("消费者: 准备消费数据\n");

// 等待数据就绪
while (__atomic_load_n(&data_ready, __ATOMIC_ACQUIRE) == 0) {
printf("消费者: 无数据,等待生产者...\n");
int ret = futex_wait_timeout(&data_ready, 0, 3000); // 3秒超时
if (ret == -1 && errno == ETIMEDOUT) {
printf("消费者: 等待超时,继续检查...\n");
}
}

// 消费数据
printf("消费者: 消费了数据 %d\n", buffer);
buffer = -1;
__atomic_store_n(&data_ready, 0, __ATOMIC_RELEASE);

// 唤醒生产者
futex_wake(&data_ready, 1);

sleep(2);
}

return NULL;
}

int main() {
pthread_t producer, consumer;

printf("=== 带超时的 FUTEX 操作示例 ===\n");
printf("生产者生产 5 个数据,消费者消费它们\n");
printf("等待操作设置 2-3 秒超时\n\n");

// 创建线程
if (pthread_create(&producer, NULL, producer_thread, NULL) != 0 ||
pthread_create(&consumer, NULL, consumer_thread, NULL) != 0) {
perror("pthread_create");
return 1;
}

// 等待线程完成
pthread_join(producer, NULL);
pthread_join(consumer, NULL);

printf("\n生产者-消费者操作完成\n");
return 0;
}

示例3:实现简单的互斥锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <errno.h>
#include <stdatomic.h>

// 简单的 futex 互斥锁实现
typedef struct {
atomic_int state; // 0: 未锁定, 1: 已锁定
} futex_mutex_t;

// 初始化互斥锁
void futex_mutex_init(futex_mutex_t *mutex) {
atomic_store(&mutex->state, 0);
}

// 加锁
void futex_mutex_lock(futex_mutex_t *mutex) {
int expected = 0;
// 尝试原子地将状态从 0 改为 1
while (!atomic_compare_exchange_weak(&mutex->state, &expected, 1)) {
// 如果失败,说明锁已被占用,等待
syscall(SYS_futex, &mutex->state, FUTEX_WAIT, 1, NULL, NULL, 0);
expected = 0; // 重置期望值
}
}

// 解锁
void futex_mutex_unlock(futex_mutex_t *mutex) {
// 原子地将状态改为 0
atomic_store(&mutex->state, 0);
// 唤醒一个等待的线程
syscall(SYS_futex, &mutex->state, FUTEX_WAKE, 1, NULL, NULL, 0);
}

// 全局变量和互斥锁
int shared_counter = 0;
futex_mutex_t my_mutex;

// 工作线程函数
void* increment_thread(void* arg) {
int thread_id = *(int*)arg;
int operations = 100000;

printf("线程 %d: 开始执行 %d 次操作\n", thread_id, operations);

for (int i = 0; i < operations; i++) {
futex_mutex_lock(&my_mutex);
// 临界区
shared_counter++;
futex_mutex_unlock(&my_mutex);
}

printf("线程 %d: 完成\n", thread_id);
return NULL;
}

int main() {
pthread_t threads&#91;4];
int thread_ids&#91;4] = {1, 2, 3, 4};
int expected_result = 400000;

printf("=== FUTEX 互斥锁实现示例 ===\n");
printf("4 个线程,每个执行 100,000 次递增操作\n");
printf("期望结果: %d\n\n", expected_result);

// 初始化互斥锁
futex_mutex_init(&my_mutex);

// 创建线程
for (int i = 0; i < 4; i++) {
if (pthread_create(&threads&#91;i], NULL, increment_thread, &thread_ids&#91;i]) != 0) {
perror("pthread_create");
return 1;
}
}

// 等待所有线程完成
for (int i = 0; i < 4; i++) {
pthread_join(threads&#91;i], NULL);
}

printf("\n实际结果: %d\n", shared_counter);
printf("结果%s正确\n", (shared_counter == expected_result) ? "" : "不");

if (shared_counter != expected_result) {
printf("可能存在竞态条件或同步问题\n");
}

return 0;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
# 编译示例(需要链接 pthread 库)
gcc -o futex_example1 example1.c -lpthread
gcc -o futex_example2 example2.c -lpthread
gcc -o futex_example3 example3.c -lpthread

# 运行示例
./futex_example1
./futex_example2
./futex_example3

重要注意事项

原子操作配合: futex 通常与原子操作(atomic operations)配合使用,确保检查和等待操作的原子性

虚假唤醒: 与条件变量一样,futex 也可能出现虚假唤醒,需要在循环中检查条件

内存序: 使用适当的内存序(memory ordering)确保同步正确性

错误处理: 始终检查 futex 调用的返回值和 errno

移植性: futex 是 Linux 特有的特性,在其他系统上不可用

调试困难: futex 相关的 bug 很难调试,建议使用成熟的同步库

futex 的优势

高性能: 无竞争时完全在用户空间操作

低延迟: 避免不必要的内核切换

可扩展性: 支持大量并发线程

灵活性: 可以实现各种同步原语

这些示例展示了 futex 的基本使用方法,从简单的同步操作到带超时机制,再到完整的互斥锁实现,帮助你理解这个强大而高效的同步机制。

futex系统调用, futex函数详解, Linux futex示例, Fast Userspace muTEX, futex编程指南, 如何使用futex, futex性能优化, 基于futex的同步机制, futex与线程同步, 高级futex技术应用

https://www.calcguide.tech/2025/09/08/futex系统调用及示例/

https://blog.csdn.net/zidier215/article/details/151332503?sharetype=blogdetail&sharerId=151332503&sharerefer=PC&sharesource=zidier215&spm=1011.2480.3001.8118

get_kernel_syms系统调用及示例

get_kernel_syms - 获取内核符号表信息(已废弃)

1. 函数介绍

get_kernel_syms 是一个已废弃的 Linux 系统调用,用于获取内核符号表的信息。它曾经被用来检索内核中导出的符号(函数和变量)的列表,包括它们的地址、类型和名称。

重要说明:这个系统调用在现代 Linux 内核中已被废弃和移除,不再推荐使用。现代系统应该使用其他方式来获取内核符号信息。

2. 函数原型

1
2
3
4
#include <linux/kernel.h>

int get_kernel_syms(struct kernel_sym *table);

3. 功能

获取内核符号表中导出符号的信息,包括符号名称、地址和类型。主要用于内核调试、模块加载和系统分析工具。

4. 参数

struct kernel_sym *table: 指向 kernel_sym 结构体数组的指针

  • 如果为 NULL:返回符号表中的符号总数

  • 如果非 NULL:将符号信息填充到该数组中

5. kernel_sym 结构体定义

1
2
3
4
5
struct kernel_sym {
unsigned long ks_addr; /* 符号地址 */
char ks_name&#91;60]; /* 符号名称(最多59个字符+null终止符)*/
};

6. 返回值

成功时:

  • 如果 table 为 NULL:返回内核符号表中的符号总数

  • 如果 table 非 NULL:返回实际填充的符号数量

失败时返回 -1,并设置 errno

7. 常见 errno 错误码

  • EPERM: 权限不足(需要 CAP_SYS_ADMIN 或 CAP_SYS_MODULE 权限)

  • EFAULT: table 指针指向无效内存地址

  • ENOMEM: 内存不足

  • ENOSYS: 系统调用不支持(现代内核中常见)

8. 相似函数,或关联函数

  • /proc/kallsyms: 现代系统中获取内核符号的标准方式

  • /proc/sys/kernel/kptr_restrict: 控制内核指针显示的设置

  • klogctl(): 控制内核日志缓冲区

  • uname(): 获取系统信息

  • sysinfo(): 获取系统统计信息

  • nm 命令:用户态工具查看目标文件符号表

  • /boot/System.map: 内核构建时生成的符号映射文件

9. 示例代码

示例1:检查系统是否支持 get_kernel_syms

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <string.h>

#ifndef SYS_get_kernel_syms
# define SYS_get_kernel_syms 177 // x86_64 架构下的系统调用号
#endif

int main() {
long result;

printf("检查系统是否支持 get_kernel_syms...\n");

// 首先尝试获取符号数量(传入 NULL)
result = syscall(SYS_get_kernel_syms, NULL);

if (result == -1) {
printf("get_kernel_syms 调用失败\n");
printf("errno = %d: %s\n", errno, strerror(errno));

switch (errno) {
case ENOSYS:
printf("系统调用已废弃或不支持\n");
break;
case EPERM:
printf("权限不足,需要特权权限\n");
break;
default:
printf("其他错误\n");
break;
}
} else {
printf("系统支持 get_kernel_syms,符号数量: %ld\n", result);
}

return 0;
}

示例2:传统使用方式(仅在旧系统上可能工作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <string.h>

#ifndef SYS_get_kernel_syms
# define SYS_get_kernel_syms 177
#endif

// 注意:现代 glibc 可能不包含这个结构体定义
struct kernel_sym {
unsigned long ks_addr;
char ks_name&#91;60];
};

int main() {
long sym_count;
struct kernel_sym *sym_table;
long result;
int i;

printf("=== get_kernel_syms 测试 ===\n");

// 第一次调用:获取符号数量
sym_count = syscall(SYS_get_kernel_syms, NULL);

if (sym_count == -1) {
printf("获取符号数量失败: %s\n", strerror(errno));
if (errno == ENOSYS) {
printf("提示: get_kernel_syms 已在现代内核中废弃\n");
printf("请使用 /proc/kallsyms 替代\n");
}
return 1;
}

printf("内核符号数量: %ld\n", sym_count);

if (sym_count == 0) {
printf("没有可用的内核符号\n");
return 0;
}

// 限制获取的符号数量以避免内存问题
if (sym_count > 1000) {
printf("符号数量过多,限制为前1000个\n");
sym_count = 1000;
}

// 分配内存
sym_table = malloc(sym_count * sizeof(struct kernel_sym));
if (sym_table == NULL) {
perror("内存分配失败");
return 1;
}

// 第二次调用:获取符号信息
result = syscall(SYS_get_kernel_syms, sym_table);

if (result == -1) {
printf("获取符号信息失败: %s\n", strerror(errno));
free(sym_table);
return 1;
}

printf("成功获取 %ld 个符号信息\n", result);

// 显示前几个符号作为示例
printf("\n前10个内核符号:\n");
printf("%-18s %s\n", "地址", "符号名称");
printf("%-18s %s\n", "----", "--------");

for (i = 0; i < result && i < 10; i++) {
printf("0x%016lx %s\n",
sym_table&#91;i].ks_addr,
sym_table&#91;i].ks_name);
}

free(sym_table);
return 0;
}

10. 现代替代方案

由于 get_kernel_syms 已被废弃,现代系统应该使用以下替代方案:

方案1:使用 /proc/kallsyms

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <stdlib.h>

int main() {
FILE *fp;
char line&#91;256];
char address&#91;32];
char type;
char symbol&#91;128];
int count = 0;

fp = fopen("/proc/kallsyms", "r");
if (fp == NULL) {
perror("打开 /proc/kallsyms 失败");
return 1;
}

printf("读取内核符号表 (/proc/kallsyms):\n");
printf("%-18s %-8s %s\n", "地址", "类型", "符号");
printf("%-18s %-8s %s\n", "----", "----", "----");

// 读取前20个符号
while (fgets(line, sizeof(line), fp) && count < 20) {
if (sscanf(line, "%s %c %s", address, &type, symbol) == 3) {
printf("%-18s %-8c %s\n", address, type, symbol);
count++;
}
}

fclose(fp);

// 检查 kptr_restrict 设置
fp = fopen("/proc/sys/kernel/kptr_restrict", "r");
if (fp != NULL) {
int restrict_level;
if (fscanf(fp, "%d", &restrict_level) == 1) {
printf("\nkptr_restrict 级别: %d\n", restrict_level);
if (restrict_level > 0) {
printf("注意: 内核指针可能被限制显示\n");
}
}
fclose(fp);
}

return 0;
}

方案2:使用 System.map 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <stdlib.h>

int main() {
FILE *fp;
char line&#91;256];
char address&#91;32];
char type;
char symbol&#91;128];
int count = 0;

// 尝试打开 System.map 文件
const char *system_map_paths&#91;] = {
"/boot/System.map-$(uname -r)", // 需要 shell 展开
"/boot/System.map",
"/lib/modules/$(uname -r)/System.map", // 需要 shell 展开
NULL
};

// 简化版本:直接尝试常见路径
fp = fopen("/boot/System.map", "r");
if (fp == NULL) {
printf("System.map 文件不可用\n");
printf("现代系统推荐使用 /proc/kallsyms\n");
return 1;
}

printf("读取 System.map 文件:\n");
printf("%-18s %-8s %s\n", "地址", "类型", "符号");
printf("%-18s %-8s %s\n", "----", "----", "----");

// 读取前10个符号
while (fgets(line, sizeof(line), fp) && count < 10) {
if (sscanf(line, "%s %c %s", address, &type, symbol) == 3) {
printf("%-18s %-8c %s\n", address, type, symbol);
count++;
}
}

fclose(fp);
return 0;
}

11. 符号类型说明

在 /proc/kallsyms 中,符号类型字符含义:

  • T/t: 代码段中的符号(大写表示全局,小写表示局部)

  • D/d: 数据段中的符号

  • B/b: BSS段中的符号

  • R/r: 只读数据段中的符号

  • A: 绝对符号

  • U: 未定义符号

  • W/w: 弱符号

12. 安全考虑

现代 Linux 系统出于安全考虑,可能会限制内核符号信息的访问:

kptr_restrict: 控制内核指针的显示

  • 0: 允许显示内核指针

  • 1: 普通用户看不到内核指针(显示为0)

  • 2: 所有用户都看不到内核指针

权限要求: 访问内核符号信息通常需要特殊权限

总结

get_kernel_syms 是一个已废弃的系统调用,现代 Linux 系统开发应该:

避免使用:该系统调用在新内核中已不可用

使用替代方案:推荐使用 /proc/kallsyms 获取内核符号信息

注意安全限制:了解 kptr_restrict 对符号显示的影响

权限管理:确保有足够的权限访问内核信息

兼容性考虑:在代码中检查系统调用是否可用

对于内核调试、模块开发和系统分析需求,现代工具链提供了更安全、更可靠的替代方案。

get_kernel_syms系统调用及示例-CSDN博客

futimesat系统调用及示例

futimens 函数详解

  1. 函数介绍

futimens 是 Linux 系统中用于设置文件时间戳的系统调用。可以把这个函数想象成一个”时间修改器”——它允许你修改文件的访问时间和修改时间,就像你可以修改照片的拍摄时间一样。

在 Linux 文件系统中,每个文件都有两个重要的时间戳:

  • 访问时间(atime): 文件最后一次被读取的时间

  • 修改时间(mtime): 文件内容最后一次被修改的时间

futimens 通过文件描述符来修改这些时间戳,这使得它比 utimes 更安全,因为文件描述符确保了你操作的是已经打开的文件,避免了竞态条件。

https://www.calcguide.tech/2025/09/08/futimesat系统调用及示例

  1. 函数原型
1
2
3
4
5
#include <fcntl.h>     /* 必须包含此头文件 */
#include <sys/stat.h> /* 或者包含此头文件 */

int futimens(int fd, const struct timespec times&#91;2]);

  1. 功能

futimens 函数用于设置指定文件描述符对应的文件的时间戳。它可以精确地设置文件的访问时间和修改时间到纳秒级别。

  1. 参数
  • fd: 已打开的文件描述符

  • times: 指向 struct timespec 数组的指针,数组包含两个元素:times[0]: 访问时间(atime)

  • times[1]: 修改时间(mtime)
    struct timespec 结构体定义:struct timespec { time_t tv_sec; /* 秒数 / long tv_nsec; / 纳秒数 */ };

  1. 特殊时间值

在 times 数组中,可以使用以下特殊值:

  • UTIME_OMIT: 保持当前时间戳不变(跳过此时间的修改)

  • UTIME_NOW: 将时间戳设置为当前时间

  1. 返回值
  • 成功: 返回 0

  • 失败: 返回 -1,并设置相应的 errno 错误码

常见错误码:

  • EBADF: fd 不是有效的文件描述符

  • EACCES: 权限不足(通常需要写权限)

  • EINVAL: times 参数无效

  • EROFS: 文件系统是只读的

  • EIO: I/O 错误

  1. 相似函数或关联函数
  • utimensat: 通过文件路径设置时间戳(更灵活,可以指定相对路径)

  • utimes: 设置文件时间戳(较老的接口,精度较低)

  • futimes: 通过文件描述符设置时间戳(较老的接口)

  • stat: 获取文件当前的时间戳信息

  • fstat: 通过文件描述符获取文件信息

  1. 示例代码

示例1:基础用法 - 设置特定时间戳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h>

int main() {
int fd;
const char *filename = "timestamp_test.txt";
struct timespec new_times&#91;2];
struct stat file_info;

// 创建并写入测试文件
fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("open");
return 1;
}

const char *content = "这是一个测试文件\n用于演示 futimens 函数\n";
write(fd, content, strlen(content));
close(fd);

// 重新打开文件以获取文件描述符
fd = open(filename, O_WRONLY);
if (fd == -1) {
perror("open");
return 1;
}

// 设置特定的时间戳
// 访问时间:2023年1月1日 12:00:00
new_times&#91;0].tv_sec = 1672545600; // Unix 时间戳
new_times&#91;0].tv_nsec = 123456789; // 纳秒

// 修改时间:2023年6月1日 15:30:45
new_times&#91;1].tv_sec = 1685626245; // Unix 时间戳
new_times&#91;1].tv_nsec = 987654321; // 纳秒

printf("=== 修改前的时间戳 ===\n");

// 获取并显示修改前的时间
if (stat(filename, &file_info) == -1) {
perror("stat");
close(fd);
return 1;
}

printf("访问时间: %s", ctime(&file_info.st_atime));
printf("修改时间: %s", ctime(&file_info.st_mtime));

// 使用 futimens 设置新的时间戳
if (futimens(fd, new_times) == -1) {
perror("futimens");
close(fd);
return 1;
}

printf("\n=== 修改后的时间戳 ===\n");

// 获取并显示修改后的时间
if (stat(filename, &file_info) == -1) {
perror("stat");
close(fd);
return 1;
}

printf("访问时间: %s", ctime(&file_info.st_atime));
printf("修改时间: %s", ctime(&file_info.st_mtime));

// 显示纳秒级精度
printf("\n=== 纳秒级精度 ===\n");
printf("访问时间: %ld.%09ld 秒\n",
file_info.st_atim.tv_sec, file_info.st_atim.tv_nsec);
printf("修改时间: %ld.%09ld 秒\n",
file_info.st_mtim.tv_sec, file_info.st_mtim.tv_nsec);

close(fd);
return 0;
}

示例2:使用特殊时间值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h>

// 显示文件时间信息的辅助函数
void show_file_times(const char *filename, const char *description) {
struct stat file_info;

if (stat(filename, &file_info) == -1) {
perror("stat");
return;
}

printf("%s:\n", description);
printf(" 访问时间: %s", ctime(&file_info.st_atime));
printf(" 修改时间: %s", ctime(&file_info.st_mtime));
printf("\n");
}

int main() {
int fd;
const char *filename = "special_times_test.txt";
struct timespec times&#91;2];

// 创建测试文件
fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("open");
return 1;
}

const char *content = "测试特殊时间值功能\n";
write(fd, content, strlen(content));
close(fd);

// 显示初始时间
show_file_times(filename, "初始时间");

// 重新打开文件
fd = open(filename, O_WRONLY);
if (fd == -1) {
perror("open");
return 1;
}

printf("=== 演示各种时间设置方式 ===\n\n");

// 1. 使用 UTIME_NOW:将两个时间都设置为当前时间
printf("1. 使用 UTIME_NOW (设置为当前时间):\n");
times&#91;0].tv_sec = 0;
times&#91;0].tv_nsec = UTIME_NOW;
times&#91;1].tv_sec = 0;
times&#91;1].tv_nsec = UTIME_NOW;

if (futimens(fd, times) == -1) {
perror("futimens UTIME_NOW");
} else {
show_file_times(filename, " 设置后");
}

sleep(2); // 等待 2 秒以便看到时间变化

// 2. 只修改访问时间为当前时间,保持修改时间不变
printf("2. 只修改访问时间为当前时间:\n");
times&#91;0].tv_nsec = UTIME_NOW; // 访问时间设为当前
times&#91;1].tv_nsec = UTIME_OMIT; // 修改时间保持不变

if (futimens(fd, times) == -1) {
perror("futimens partial update");
} else {
show_file_times(filename, " 设置后");
}

sleep(2);

// 3. 只修改修改时间为特定时间,保持访问时间不变
printf("3. 只修改修改时间为特定时间:\n");
times&#91;0].tv_nsec = UTIME_OMIT; // 访问时间保持不变
times&#91;1].tv_sec = 1609459200; // 2021年1月1日 00:00:00
times&#91;1].tv_nsec = 500000000; // 500毫秒

if (futimens(fd, times) == -1) {
perror("futimens specific mtime");
} else {
show_file_times(filename, " 设置后");
}

close(fd);
return 0;
}

示例3:批量文件时间戳管理工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>

// 文件时间信息结构体
struct file_time_info {
char filename&#91;256];
time_t atime;
time_t mtime;
off_t size;
};

// 获取文件时间信息
int get_file_time_info(const char *filepath, struct file_time_info *info) {
struct stat file_stat;

if (stat(filepath, &file_stat) == -1) {
return -1;
}

strncpy(info->filename, filepath, sizeof(info->filename) - 1);
info->filename&#91;sizeof(info->filename) - 1] = '\0';
info->atime = file_stat.st_atime;
info->mtime = file_stat.st_mtime;
info->size = file_stat.st_size;

return 0;
}

// 设置文件时间戳
int set_file_times(const char *filepath, time_t new_atime, time_t new_mtime) {
int fd;
struct timespec times&#91;2];

fd = open(filepath, O_WRONLY);
if (fd == -1) {
// 如果写打开失败,尝试只读打开(某些文件可能只需要读权限)
fd = open(filepath, O_RDONLY);
if (fd == -1) {
return -1;
}
}

times&#91;0].tv_sec = new_atime;
times&#91;0].tv_nsec = 0;
times&#91;1].tv_sec = new_mtime;
times&#91;1].tv_nsec = 0;

int result = futimens(fd, times);
close(fd);

return result;
}

// 打印文件时间信息
void print_file_info(const struct file_time_info *info) {
printf("文件: %s\n", info->filename);
printf(" 大小: %ld 字节\n", (long)info->size);
printf(" 访问时间: %s", ctime(&info->atime));
printf(" 修改时间: %s", ctime(&info->mtime));
}

// 将目录中所有文件的时间戳设置为相同时间
int sync_directory_times(const char *dirpath, time_t target_time) {
DIR *dir;
struct dirent *entry;
char filepath&#91;512];
int count = 0;
int errors = 0;

dir = opendir(dirpath);
if (dir == NULL) {
perror("opendir");
return -1;
}

printf("正在同步目录 '%s' 中文件的时间戳...\n", dirpath);
printf("目标时间: %s\n", ctime(&target_time));

while ((entry = readdir(dir)) != NULL) {
// 跳过当前目录和父目录
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}

// 构造完整文件路径
snprintf(filepath, sizeof(filepath), "%s/%s", dirpath, entry->d_name);

// 设置文件时间戳
if (set_file_times(filepath, target_time, target_time) == 0) {
printf("✓ 成功: %s\n", entry->d_name);
count++;
} else {
printf("✗ 失败: %s (%s)\n", entry->d_name, strerror(errno));
errors++;
}
}

closedir(dir);

printf("\n处理完成: %d 个文件成功, %d 个文件失败\n", count, errors);
return errors == 0 ? 0 : -1;
}

int main(int argc, char *argv&#91;]) {
if (argc < 2) {
printf("用法: %s <文件或目录路径> &#91;目标时间戳]\n", argv&#91;0]);
printf("示例: %s /path/to/file\n", argv&#91;0]);
printf(" %s /path/to/directory 1672531200\n", argv&#91;0]);
return 1;
}

const char *path = argv&#91;1];
time_t target_time;

// 如果提供了目标时间戳,使用它;否则使用当前时间
if (argc > 2) {
target_time = (time_t)atol(argv&#91;2]);
} else {
target_time = time(NULL);
}

// 检查路径是文件还是目录
struct stat path_stat;
if (stat(path, &path_stat) == -1) {
perror("stat");
return 1;
}

if (S_ISDIR(path_stat.st_mode)) {
// 处理目录
return sync_directory_times(path, target_time);
} else {
// 处理单个文件
struct file_time_info before_info, after_info;

printf("=== 文件时间戳修改工具 ===\n\n");

// 获取修改前的信息
if (get_file_time_info(path, &before_info) == -1) {
perror("获取文件信息失败");
return 1;
}

printf("修改前:\n");
print_file_info(&before_info);

// 设置新的时间戳
if (set_file_times(path, target_time, target_time) == -1) {
perror("设置时间戳失败");
return 1;
}

// 获取修改后的信息
if (get_file_time_info(path, &after_info) == -1) {
perror("获取修改后信息失败");
return 1;
}

printf("\n修改后:\n");
print_file_info(&after_info);

printf("\n时间戳修改完成!\n");
}

return 0;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 编译示例程序
gcc -o futimens_example1 example1.c
gcc -o futimens_example2 example2.c
gcc -o futimens_example3 example3.c

# 运行示例
./futimens_example1
./futimens_example2

# 运行文件时间管理工具
./futimens_example3 test_file.txt
./futimens_example3 /path/to/directory
./futimens_example3 test_file.txt 1672531200

注意事项

权限要求: 通常需要对文件有写权限才能修改时间戳,但某些系统允许文件所有者修改自己的文件时间戳

时间精度: futimens 支持纳秒级精度,但实际精度取决于文件系统

文件系统支持: 不是所有文件系统都支持纳秒级时间戳精度

特殊值: 正确使用 UTIME_OMIT 和 UTIME_NOW 可以实现灵活的时间控制

错误处理: 始终检查返回值,因为权限不足或只读文件系统会导致操作失败

常见使用场景

备份系统: 设置备份文件的时间戳与源文件一致

构建系统: 确保生成文件的时间戳正确,避免不必要的重新编译

文件同步: 同步不同系统间的文件时间戳

测试工具: 在测试中模拟不同时间点的文件状态

数字取证: 修改文件时间戳用于取证分析(需谨慎使用)

这些示例展示了 futimens 函数的各种使用方法,从基本的时间戳设置到高级的批量处理工具,帮助你全面掌握这个重要的文件操作函数。

https://blog.csdn.net/zidier215/article/details/151332377?sharetype=blogdetail&sharerId=151332377&sharerefer=PC&sharesource=zidier215&spm=1011.2480.3001.8118

get_mempolicy系统调用及示例

get_mempolicy 函数详解

get_mempolicy 是 Linux 系统中用于查询 NUMA 内存分配策略的系统调用,可获取进程或指定内存区域的 NUMA 节点分配策略。该函数支持查询默认策略、特定地址策略及允许使用的 NUMA 节点集合,适用于优化多核 NUMA 架构下的内存访问性能。函数通过 mode 参数返回策略类型(如默认、优先节点、绑定节点等),nodemask 返回节点掩码,flags 控制查询行为。典型应用场景包括高性能计算和大内存应用,需配合 set_mempolicy 和 mbind 等函数使用。

https://www.calcguide.tech/2025/09/08/get-mempolicy系统调用及示例/

  1. 函数介绍

get_mempolicy 是 Linux 系统中用于获取内存策略(Memory Policy)的系统调用。可以把内存策略想象成”内存分配的导航系统”——它告诉操作系统应该将进程的内存分配到哪个 NUMA 节点上。

在现代多核系统中,特别是 NUMA(Non-Uniform Memory Access)架构中,不同的 CPU 核心访问不同内存节点的速度是不一样的。就像你去超市买东西,有些商品在你附近的货架上(访问快),有些在远处的货架上(访问慢)。内存策略就是告诉系统:“优先在我附近的内存区域分配内存”,从而提高程序性能。

get_mempolicy 允许你查询当前的内存分配策略,了解系统是如何为你的进程分配内存的。

  1. 函数原型
1
2
3
4
5
#include <numaif.h>

long get_mempolicy(int *mode, unsigned long *nodemask,
unsigned long maxnode, void *addr, unsigned long flags);

  1. 功能

get_mempolicy 函数用于获取当前进程或指定内存地址的内存分配策略。它可以查询:

  • 进程默认的内存策略

  • 特定内存地址的内存策略

  • 系统支持的 NUMA 节点信息

  1. 参数
  • mode: 指向整型变量的指针,用于存储返回的策略模式

  • nodemask: 指向节点掩码数组的指针,用于存储策略涉及的 NUMA 节点

  • maxnode: nodemask 数组的最大节点数

  • addr: 指定内存地址(可选,用于查询特定地址的策略)

  • flags: 控制操作行为的标志位

  1. 策略模式(mode 参数的可能值)

模式值说明MPOL_DEFAULT0默认策略,遵循系统默认行为MPOL_PREFERRED1优先分配到指定节点MPOL_BIND2严格绑定到指定节点集合MPOL_INTERLEAVE3在指定节点间交错分配MPOL_LOCAL4分配到本地节点(进程运行的节点)

  1. 标志位(flags 参数)

标志说明0查询进程默认策略MPOL_F_NODE返回节点信息MPOL_F_ADDR查询指定地址 addr 的策略MPOL_F_MEMS_ALLOWED返回进程允许使用的节点集合

  1. 返回值
  • 成功: 返回 0

  • 失败: 返回 -1,并设置相应的 errno 错误码

常见错误码:

  • EFAULT: 地址参数无效

  • EINVAL: 参数无效

  • ENOMEM: 内存不足

  1. 相似函数或关联函数
  • set_mempolicy: 设置内存策略

  • mbind: 为特定内存区域设置内存策略

  • getcpu: 获取当前 CPU 和 NUMA 节点信息

  • numa_ functions*: libnuma 库提供的 NUMA 操作函数

  • mbind: 绑定内存到特定节点

  1. 示例代码

示例1:基础用法 - 获取进程默认内存策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <numaif.h>
#include <string.h>

// 将节点掩码转换为可读的字符串
void print_nodemask(unsigned long *nodemask, unsigned long maxnode) {
printf("节点掩码: ");
int printed = 0;

for (unsigned long i = 0; i < maxnode; i++) {
if (nodemask&#91;i / (sizeof(unsigned long) * 8)] &
(1UL << (i % (sizeof(unsigned long) * 8)))) {
if (printed > 0) printf(",");
printf("%lu", i);
printed++;
}
}

if (printed == 0) {
printf("(无节点)");
}
printf("\n");
}

// 获取策略模式的字符串描述
const char* get_policy_name(int mode) {
switch (mode) {
case MPOL_DEFAULT:
return "MPOL_DEFAULT (默认策略)";
case MPOL_PREFERRED:
return "MPOL_PREFERRED (优先节点)";
case MPOL_BIND:
return "MPOL_BIND (绑定节点)";
case MPOL_INTERLEAVE:
return "MPOL_INTERLEAVE (交错分配)";
case MPOL_LOCAL:
return "MPOL_LOCAL (本地节点)";
default:
return "未知策略";
}
}

int main() {
int mode;
unsigned long nodemask&#91;16]; // 支持最多 16 * sizeof(unsigned long) * 8 个节点
unsigned long maxnode = sizeof(nodemask) * 8;

printf("=== 获取进程默认内存策略 ===\n\n");

// 获取当前进程的默认内存策略
if (get_mempolicy(&mode, nodemask, maxnode, NULL, 0) == -1) {
perror("get_mempolicy");
printf("可能的原因:\n");
printf("1. 系统不支持 NUMA\n");
printf("2. 未链接 libnuma 库\n");
printf("3. 内核不支持此功能\n");
return 1;
}

printf("当前进程内存策略:\n");
printf("策略模式: %s (%d)\n", get_policy_name(mode), mode);
print_nodemask(nodemask, maxnode);

// 获取允许使用的节点集合
printf("\n=== 允许使用的 NUMA 节点 ===\n");
memset(nodemask, 0, sizeof(nodemask));
if (get_mempolicy(&mode, nodemask, maxnode, NULL, MPOL_F_MEMS_ALLOWED) == 0) {
printf("允许的节点: ");
print_nodemask(nodemask, maxnode);
} else {
perror("get_mempolicy MPOL_F_MEMS_ALLOWED");
}

return 0;
}

示例2:查询特定内存地址的策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <numaif.h>
#include <string.h>

int main() {
int mode;
unsigned long nodemask&#91;16];
unsigned long maxnode = sizeof(nodemask) * 8;
char *memory_block;
size_t block_size = 1024 * 1024; // 1MB

printf("=== 查询特定内存地址的策略 ===\n\n");

// 分配内存块
memory_block = mmap(NULL, block_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (memory_block == MAP_FAILED) {
perror("mmap");
return 1;
}

printf("分配内存块: %p - %p (大小: %zu 字节)\n",
memory_block, memory_block + block_size - 1, block_size);

// 查询整个内存块的策略
printf("\n--- 查询内存块起始地址的策略 ---\n");
if (get_mempolicy(&mode, nodemask, maxnode, memory_block, MPOL_F_ADDR) == 0) {
printf("地址 %p 的策略: %d\n", memory_block, mode);
} else {
perror("get_mempolicy for memory block");
}

// 查询内存块中间地址的策略
printf("\n--- 查询内存块中间地址的策略 ---\n");
char *middle_addr = memory_block + block_size / 2;
if (get_mempolicy(&mode, nodemask, maxnode, middle_addr, MPOL_F_ADDR) == 0) {
printf("地址 %p 的策略: %d\n", middle_addr, mode);
} else {
perror("get_mempolicy for middle address");
}

// 使用内存
memset(memory_block, 0xAA, block_size);
printf("\n已使用内存块\n");

// 再次查询策略
printf("\n--- 使用内存后查询策略 ---\n");
if (get_mempolicy(&mode, nodemask, maxnode, memory_block, MPOL_F_ADDR) == 0) {
printf("地址 %p 的策略: %d\n", memory_block, mode);
} else {
perror("get_mempolicy after usage");
}

// 释放内存
if (munmap(memory_block, block_size) == -1) {
perror("munmap");
return 1;
}

printf("\n内存块已释放\n");
return 0;
}

示例3:完整的 NUMA 信息查询工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <numaif.h>
#include <sched.h>
#include <string.h>

// 简单的 CPU 和 NUMA 信息查询
void print_cpu_and_numa_info() {
#ifdef SYS_getcpu
unsigned cpu, node;
if (syscall(SYS_getcpu, &cpu, &node, NULL) == 0) {
printf("当前运行在 CPU %u, NUMA 节点 %u\n", cpu, node);
} else {
printf("无法获取 CPU/NUMA 信息\n");
}
#else
printf("系统不支持 getcpu 系统调用\n");
#endif
}

// 显示系统 NUMA 拓扑信息
void print_numa_topology() {
long max_nodes = sysconf(_SC_NPROCESSORS_ONLN);
printf("系统在线 CPU 数: %ld\n", max_nodes);

// 尝试获取 NUMA 节点数
FILE *fp = fopen("/sys/devices/system/node/online", "r");
if (fp) {
char buffer&#91;256];
if (fgets(buffer, sizeof(buffer), fp)) {
printf("在线 NUMA 节点: %s", buffer);
}
fclose(fp);
} else {
printf("无法读取 NUMA 节点信息\n");
}
}

// 详细分析内存策略
void analyze_memory_policy() {
int mode;
unsigned long nodemask&#91;16];
unsigned long maxnode = sizeof(nodemask) * 8;

printf("\n=== 详细内存策略分析 ===\n");

// 1. 获取默认策略
if (get_mempolicy(&mode, nodemask, maxnode, NULL, 0) == 0) {
printf("1. 进程默认策略: %d\n", mode);
}

// 2. 获取允许的节点
memset(nodemask, 0, sizeof(nodemask));
if (get_mempolicy(&mode, nodemask, maxnode, NULL, MPOL_F_MEMS_ALLOWED) == 0) {
printf("2. 允许使用的节点: ");
int count = 0;
for (unsigned long i = 0; i < maxnode; i++) {
if (nodemask&#91;i / (sizeof(unsigned long) * 8)] &
(1UL << (i % (sizeof(unsigned long) * 8)))) {
if (count > 0) printf(", ");
printf("%lu", i);
count++;
}
}
printf("\n");
}

// 3. 获取本地策略
memset(nodemask, 0, sizeof(nodemask));
if (get_mempolicy(&mode, nodemask, maxnode, NULL, MPOL_F_NODE) == 0) {
printf("3. 本地节点策略: %d\n", mode);
}
}

// 测试不同内存分配的策略
void test_memory_allocation_policies() {
printf("\n=== 内存分配策略测试 ===\n");

// 分配小块内存
char *small_alloc = malloc(1024);
if (small_alloc) {
printf("小块内存分配 (%p): ", small_alloc);
int mode;
unsigned long nodemask&#91;16];
if (get_mempolicy(&mode, nodemask, sizeof(nodemask) * 8,
small_alloc, MPOL_F_ADDR) == 0) {
printf("策略 %d\n", mode);
} else {
printf("无法查询策略\n");
}
free(small_alloc);
}

// 分配大块内存
char *large_alloc = malloc(10 * 1024 * 1024); // 10MB
if (large_alloc) {
printf("大块内存分配 (%p): ", large_alloc);
int mode;
unsigned long nodemask&#91;16];
if (get_mempolicy(&mode, nodemask, sizeof(nodemask) * 8,
large_alloc, MPOL_F_ADDR) == 0) {
printf("策略 %d\n", mode);
} else {
printf("无法查询策略\n");
}
free(large_alloc);
}
}

int main() {
printf("=== NUMA 内存策略查询工具 ===\n\n");

// 显示基本信息
print_cpu_and_numa_info();
print_numa_topology();

// 分析内存策略
analyze_memory_policy();

// 测试内存分配
test_memory_allocation_policies();

printf("\n=== 工具使用说明 ===\n");
printf("此工具显示当前进程的 NUMA 内存策略信息。\n");
printf("如果显示错误,可能原因:\n");
printf("1. 系统不是 NUMA 架构\n");
printf("2. 未安装或链接 libnuma 库\n");
printf("3. 内核版本不支持 NUMA 功能\n");

return 0;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 编译示例程序(需要链接 libnuma)
gcc -o get_mempolicy_example1 example1.c -lnuma
gcc -o get_mempolicy_example2 example2.c -lnuma
gcc -o get_mempolicy_example3 example3.c -lnuma

# 如果系统没有 libnuma,可以尝试:
gcc -o get_mempolicy_example1 example1.c
gcc -o get_mempolicy_example2 example2.c

# 运行示例
./get_mempolicy_example1
./get_mempolicy_example2
./get_mempolicy_example3

安装 libnuma(如果需要)

1
2
3
4
5
6
7
8
9
# Ubuntu/Debian
sudo apt-get install libnuma-dev

# CentOS/RHEL
sudo yum install numactl-devel

# Fedora
sudo dnf install numactl-devel

重要注意事项

系统要求: 需要 NUMA 支持的硬件和内核

库依赖: 通常需要链接 libnuma 库

权限: 一般不需要特殊权限

移植性: NUMA 功能是 Linux 特有的

性能影响: 查询内存策略本身开销很小

策略模式详解

MPOL_DEFAULT: 使用系统默认的内存分配策略

MPOL_PREFERRED: 优先在指定节点分配内存,如果该节点不可用则在其他节点分配

MPOL_BIND: 严格限制只能在指定节点集合中分配内存

MPOL_INTERLEAVE: 在多个节点间交错分配内存(适合大内存应用)

MPOL_LOCAL: 总是在进程运行的本地节点分配内存

实际应用场景

高性能计算: 优化大规模数值计算的内存访问模式

数据库系统: 将数据分配到合适的 NUMA 节点以提高访问速度

Web 服务器: 优化多线程应用的内存局部性

科学计算: 大型矩阵运算的内存布局优化

虚拟化环境: 为虚拟机合理分配物理内存节点

这些示例展示了 get_mempolicy 函数的各种使用方法,从基本的策略查询到完整的 NUMA 信息分析工具,帮助你理解和掌握 Linux NUMA 内存管理机制。

get_mempolicy系统调用, Linux get_mempolicy函数, get_mempolicy使用示例, 查询内存策略Linux, 理解get_mempolicy工作原理, Linux内存管理API, 系统调用与内存分配, 高级Linux编程技巧, get_mempolicy参数解析, 优化Linux性能的系统调用

get_mempolicy系统调用及示例-CSDN博客

https://www.calcguide.tech/2025/09/08/get-mempolicy系统调用及示例/

get_robust_list系统调用及示例

get_robust_list - 获取进程的健壮互斥锁列表

1. 函数介绍

get_robust_list 是一个 Linux 系统调用,用于获取指定进程的健壮互斥锁(robust futex)列表。健壮互斥锁是 Linux 提供的一种特殊类型的 futex,能够在持有锁的进程异常终止时自动清理锁状态,防止其他进程无限期等待。

这个机制主要用于解决多线程程序中,当持有互斥锁的线程意外崩溃时,其他等待该锁的线程可能会永远阻塞的问题。

https://blog.csdn.net/zidier215/article/details/151332225?sharetype=blogdetail&sharerId=151332225&sharerefer=PC&sharesource=zidier215&spm=1011.2480.3001.8118

https://www.calcguide.tech/2025/09/08/get-robust-list系统调用及示例/

2. 函数原型

1
2
3
4
5
6
#include <linux/futex.h>
#include <sys/syscall.h>
#include <unistd.h>

long get_robust_list(int pid, struct robust_list_head **head_ptr, size_t *len_ptr);

注意:该函数不是标准 C 库的一部分,需要通过 syscall() 调用。

3. 功能

获取指定进程的健壮互斥锁列表信息。返回的信息包括指向健壮列表头的指针和列表的长度。

4. 参数

int pid: 进程 ID

  • 0: 获取当前进程的健壮列表

  • 正整数: 获取指定进程的健壮列表

struct robust_list_head **head_ptr: 指向指针的指针,用于接收健壮列表头的地址

size_t *len_ptr: 指向 size_t 的指针,用于接收列表长度

5. robust_list_head 结构体定义

1
2
3
4
5
6
7
8
9
10
struct robust_list {
struct robust_list *next;
};

struct robust_list_head {
struct robust_list list;
long futex_offset;
unsigned long list_op_pending;
};

6. 返回值

  • 成功时返回 0

  • 失败时返回 -1,并设置 errno

7. 常见 errno 错误码

  • EPERM: 权限不足(尝试获取其他进程信息时需要适当权限)

  • ESRCH: 指定的进程不存在

  • EINVAL: 无效的参数

  • EFAULT: 指针参数指向无效内存地址

  • ENOSYS: 系统不支持该功能

8. 相似函数,或关联函数

  • set_robust_list(): 设置当前进程的健壮互斥锁列表

  • futex(): futex 系统调用

  • pthread_mutexattr_setrobust(): 设置 pthread 互斥锁为健壮类型

  • pthread_mutex_consistent(): 标记健壮互斥锁为一致状态

  • /proc/[pid]/maps: 查看进程内存映射

  • /proc/[pid]/status: 查看进程状态信息

9. 示例代码

示例1:基本使用 - 获取当前进程的健壮列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <errno.h>
#include <string.h>

#ifndef SYS_get_robust_list
# define SYS_get_robust_list 275 // x86_64 架构下的系统调用号
#endif

int main() {
struct robust_list_head *head;
size_t len;
long result;

printf("=== 获取当前进程的健壮互斥锁列表 ===\n");
printf("当前进程 PID: %d\n", getpid());

// 获取当前进程的健壮列表
result = syscall(SYS_get_robust_list, 0, &head, &len);

if (result == -1) {
printf("get_robust_list 调用失败\n");
printf("errno = %d: %s\n", errno, strerror(errno));

switch (errno) {
case EPERM:
printf("权限不足\n");
break;
case ENOSYS:
printf("系统不支持健壮互斥锁功能\n");
break;
default:
printf("其他错误\n");
break;
}
return 1;
}

printf("成功获取健壮列表信息:\n");
printf(" 列表头地址: %p\n", (void*)head);
printf(" 列表长度: %zu 字节\n", len);

if (head == NULL) {
printf(" 当前进程没有设置健壮列表\n");
} else {
printf(" 已设置健壮列表\n");
}

return 0;
}

示例2:健壮互斥锁的实际使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <sys/syscall.h>
#include <linux/futex.h>

#ifndef SYS_get_robust_list
# define SYS_get_robust_list 275
#endif
#ifndef SYS_set_robust_list
# define SYS_set_robust_list 275
#endif

pthread_mutex_t robust_mutex = PTHREAD_MUTEX_INITIALIZER;

void* worker_thread(void *arg) {
int thread_id = *(int*)arg;
int ret;

printf("线程 %d: 尝试获取健壮互斥锁\n", thread_id);

ret = pthread_mutex_lock(&robust_mutex);
if (ret == EOWNERDEAD) {
printf("线程 %d: 检测到锁持有者已死亡\n", thread_id);
printf("线程 %d: 标记互斥锁为一致状态\n", thread_id);
pthread_mutex_consistent(&robust_mutex);
} else if (ret != 0) {
printf("线程 %d: 获取锁失败: %s\n", thread_id, strerror(ret));
return NULL;
}

printf("线程 %d: 成功获取锁,执行临界区代码\n", thread_id);

// 模拟一些工作
sleep(2);

printf("线程 %d: 释放锁\n", thread_id);
pthread_mutex_unlock(&robust_mutex);

return NULL;
}

void check_robust_list() {
struct robust_list_head *head;
size_t len;
long result;

result = syscall(SYS_get_robust_list, 0, &head, &len);
if (result == 0) {
printf("健壮列表状态:\n");
printf(" 列表头: %p\n", (void*)head);
printf(" 长度: %zu 字节\n", len);
}
}

int main() {
pthread_t thread1, thread2;
int id1 = 1, id2 = 2;
int ret;

printf("=== 健壮互斥锁演示 ===\n");

// 设置互斥锁为健壮类型
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
pthread_mutex_init(&robust_mutex, &attr);

printf("已创建健壮互斥锁\n");
check_robust_list();

// 创建第一个线程
ret = pthread_create(&thread1, NULL, worker_thread, &id1);
if (ret != 0) {
printf("创建线程1失败: %s\n", strerror(ret));
return 1;
}

// 等待一段时间,让第一个线程获取锁
sleep(1);

// 创建第二个线程
ret = pthread_create(&thread2, NULL, worker_thread, &id2);
if (ret != 0) {
printf("创建线程2失败: %s\n", strerror(ret));
return 1;
}

// 等待线程完成
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);

printf("所有线程执行完成\n");
check_robust_list();

// 清理
pthread_mutex_destroy(&robust_mutex);
pthread_mutexattr_destroy(&attr);

return 0;
}

示例3:获取其他进程的健壮列表信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>

#ifndef SYS_get_robust_list
# define SYS_get_robust_list 275
#endif

void check_process_robust_list(pid_t pid) {
struct robust_list_head *head;
size_t len;
long result;

printf("检查进程 %d 的健壮列表:\n", pid);

result = syscall(SYS_get_robust_list, pid, &head, &len);

if (result == -1) {
printf(" 获取失败: %s\n", strerror(errno));
switch (errno) {
case EPERM:
printf(" 权限不足,无法查看其他进程信息\n");
break;
case ESRCH:
printf(" 进程不存在\n");
break;
default:
break;
}
} else {
printf(" 成功获取信息:\n");
printf(" 列表头地址: %p\n", (void*)head);
printf(" 列表长度: %zu 字节\n", len);

if (head == NULL) {
printf(" 该进程未设置健壮列表\n");
} else {
printf(" 该进程已设置健壮列表\n");
}
}
printf("\n");
}

int main() {
pid_t child_pid;
int status;

printf("=== 进程健壮列表检查 ===\n");

// 检查当前进程
check_process_robust_list(0); // 当前进程

// 检查 init 进程
check_process_robust_list(1);

// 创建子进程进行测试
child_pid = fork();

if (child_pid == -1) {
perror("fork 失败");
return 1;
}

if (child_pid == 0) {
// 子进程
printf("子进程 PID: %d\n", getpid());

// 子进程睡眠一段时间
sleep(5);

exit(0);
} else {
// 父进程
printf("父进程 PID: %d\n", getpid());
printf("子进程 PID: %d\n", child_pid);

// 检查子进程
sleep(1); // 等待子进程启动
check_process_robust_list(child_pid);

// 等待子进程结束
waitpid(child_pid, &status, 0);
printf("子进程已结束\n");

// 再次检查已结束的进程
check_process_robust_list(child_pid);
}

return 0;
}

10. 健壮互斥锁的工作原理

健壮互斥锁的核心机制:

列表维护: 每个进程维护一个健壮互斥锁列表

异常检测: 当持有锁的进程异常终止时,内核检测到

自动清理: 内核自动将锁标记为”所有者死亡”状态

状态恢复: 下一个尝试获取锁的线程收到 EOWNERDEAD 错误

一致性标记: 获得锁的线程必须调用 pthread_mutex_consistent() 标记锁为一致状态

11. pthread 健壮互斥锁使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

pthread_mutex_t robust_mutex;

void setup_robust_mutex() {
pthread_mutexattr_t attr;

pthread_mutexattr_init(&attr);
pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
pthread_mutex_init(&robust_mutex, &attr);
pthread_mutexattr_destroy(&attr);
}

void use_robust_mutex(int thread_id) {
int ret;

ret = pthread_mutex_lock(&robust_mutex);
switch (ret) {
case 0:
printf("线程 %d: 成功获取锁\n", thread_id);
break;

case EOWNERDEAD:
printf("线程 %d: 检测到锁所有者死亡\n", thread_id);
// 检查数据一致性并恢复
// ... 数据恢复代码 ...
pthread_mutex_consistent(&robust_mutex);
printf("线程 %d: 锁状态已恢复\n", thread_id);
break;

case ENOTRECOVERABLE:
printf("线程 %d: 锁不可恢复,需要重新初始化\n", thread_id);
pthread_mutex_destroy(&robust_mutex);
setup_robust_mutex();
return;

default:
printf("线程 %d: 获取锁失败: %s\n", thread_id, strerror(ret));
return;
}

// 临界区代码
printf("线程 %d: 执行临界区操作\n", thread_id);
sleep(1);

pthread_mutex_unlock(&robust_mutex);
printf("线程 %d: 释放锁\n", thread_id);
}

12. 实际应用场景

健壮互斥锁主要用于:

  • 多进程共享内存: 防止进程崩溃导致其他进程死锁

  • 关键系统服务: 提高系统稳定性和可靠性

  • 数据库系统: 保护共享数据结构

  • 实时系统: 确保系统的可预测性

  • 高可用性应用: 减少单点故障影响

总结

get_robust_list 是管理健壮互斥锁的重要系统调用,关键要点:

健壮性保障: 防止进程崩溃导致的死锁问题

异常处理: 提供 EOWNERDEAD 机制处理锁所有者死亡情况

权限控制: 访问其他进程信息需要适当权限

现代替代: pthread 提供了更高层的健壮互斥锁接口

系统安全: 需要谨慎使用,避免安全风险

在编写高可靠性多线程程序时,健壮互斥锁是一个重要的同步原语,能够显著提高程序的健壮性。

get_thread_area系统调用及示例

get_thread_area 函数详解

  1. 函数介绍

get_thread_area 是 Linux 系统中用于获取线程本地存储(Thread Local Storage, TLS)描述符的系统调用。可以把 TLS 想象成”每个线程的私人储物柜”——每个线程都有自己独立的存储空间,存放线程特定的数据,其他线程无法访问。

在多线程程序中,有时我们需要为每个线程维护一些独立的数据,比如线程 ID、错误码、用户数据等。TLS 提供了一种机制,让每个线程都有自己的变量副本,避免了线程间的数据竞争和同步问题。

get_thread_area 允许你查询线程的 TLS 描述符信息,了解线程本地存储的配置情况。这就像查看你的储物柜配置信息一样。

get_thread_area是Linux系统中用于获取线程本地存储(TLS)描述符的系统调用,主要用于i386架构。该调用允许查询线程特定的存储区域配置,通过user_desc结构体返回条目号、基地址、大小限制等TLS信息。在现代64位系统中,该功能通常被arch_prctl或标准的TLS机制替代。示例代码展示了在32位和64位系统上查询TLS信息的不同方法,以及使用__thread关键字定义线程局部变量的实践。

  1. 函数原型
1
2
3
4
5
#include <asm/ldt.h>    /* 或者 <sys/ldt.h> */
#include <sys/syscall.h>

int get_thread_area(struct user_desc *u_info);

注意:这个函数是 i386 架构特有的系统调用,在现代 64 位系统上可能不可用或行为不同。

  1. 功能

get_thread_area 函数用于获取线程的 TLS(Thread Local Storage)描述符信息。它主要在 i386 架构上使用,用于查询线程本地存储区域的配置。

  1. 参数
  • u_info: 指向 struct user_desc 结构体的指针,用于存储返回的 TLS 描述符信息
  1. struct user_desc 结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
struct user_desc {
unsigned int entry_number; /* TLS 描述符条目号 */
unsigned long base_addr; /* TLS 区域基地址 */
unsigned int limit; /* TLS 区域大小限制 */
unsigned int seg_32bit:1; /* 32 位段标志 */
unsigned int contents:2; /* 段内容类型 */
unsigned int read_exec_only:1; /* 只读执行标志 */
unsigned int limit_in_pages:1; /* 限制单位标志 */
unsigned int seg_not_present:1; /* 段不存在标志 */
unsigned int useable:1; /* 可用标志 */
unsigned int lm:1; /* 长模式标志 (64 位) */
};

  1. 返回值
  • 成功: 返回 0

  • 失败: 返回 -1,并设置相应的 errno 错误码

常见错误码:

  • EFAULT: u_info 指针无效

  • EINVAL: 参数无效

  • ENOSYS: 系统不支持此调用

  1. 相似函数或关联函数
  • set_thread_area: 设置线程本地存储描述符

  • arch_prctl: 在 x86-64 上管理 TLS(现代替代方案)

  • pthread_getspecific/pthread_setspecific: POSIX 线程特定数据

  • __thread: GCC 的线程局部存储关键字

  • thread_local: C11 标准的线程局部存储

  1. 重要说明

⚠️ 注意: get_thread_area 主要用于 i386 (32位 x86) 架构,在现代 64 位系统上:

可能不被支持

行为可能不同

建议使用 arch_prctl 或标准的 TLS 机制

  1. 示例代码

示例1:基础用法 - 查询 TLS 信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <asm/ldt.h>
#include <errno.h>
#include <string.h>

#ifdef __i386__
// 32位系统上的实现
int get_thread_area_wrapper(struct user_desc *u_info) {
return syscall(SYS_get_thread_area, u_info);
}

void print_tls_info(const struct user_desc *desc) {
printf("TLS 描述符信息:\n");
printf(" 条目号: %u\n", desc->entry_number);
printf(" 基地址: 0x%lx\n", desc->base_addr);
printf(" 大小限制: %u\n", desc->limit);
printf(" 32位段: %s\n", desc->seg_32bit ? "是" : "否");
printf(" 只读执行: %s\n", desc->read_exec_only ? "是" : "否");
printf(" 页面单位: %s\n", desc->limit_in_pages ? "是" : "否");
printf(" 段存在: %s\n", !desc->seg_not_present ? "是" : "否");
printf(" 可用: %s\n", desc->useable ? "是" : "否");
}

int main() {
struct user_desc tls_desc;

printf("=== 获取线程本地存储信息 ===\n\n");

// 尝试获取 TLS 信息
// 注意:entry_number 需要设置为要查询的条目号
// 通常 TLS 条目号由系统分配
tls_desc.entry_number = -1; // 请求系统分配

if (get_thread_area_wrapper(&tls_desc) == 0) {
printf("成功获取 TLS 信息:\n");
print_tls_info(&tls_desc);
} else {
printf("获取 TLS 信息失败: %s\n", strerror(errno));
printf("这在 64 位系统上是正常的\n");
return 1;
}

return 0;
}

#else

// 64位系统上的替代实现
#include <asm/prctl.h>
#include <sys/prctl.h>

int main() {
unsigned long fs_base;

printf("=== 现代系统 TLS 信息查询 ===\n\n");
printf("在 64 位系统上,使用 arch_prctl 替代 get_thread_area\n\n");

// 获取 FS 段基地址(TLS 区域)
if (arch_prctl(ARCH_GET_FS, &fs_base) == 0) {
printf("FS 段基地址 (TLS 区域): 0x%lx\n", fs_base);
} else {
perror("arch_prctl ARCH_GET_FS");
}

// 获取 GS 段基地址
if (arch_prctl(ARCH_GET_GS, &fs_base) == 0) {
printf("GS 段基地址: 0x%lx\n", fs_base);
} else {
perror("arch_prctl ARCH_GET_GS");
}

printf("\n说明:现代 64 位系统使用 FS/GS 寄存器实现 TLS\n");
return 0;
}
#endif

示例2:TLS 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

// 使用 GCC 的 __thread 关键字定义线程局部变量
__thread int thread_id = 0;
__thread char thread_name&#91;32] = "未命名线程";
__thread int operation_count = 0;

// 普通全局变量(所有线程共享)
int global_counter = 0;

// 线程函数
void* worker_thread(void* arg) {
int local_thread_num = *(int*)arg;

// 为每个线程设置不同的 TLS 值
thread_id = local_thread_num;
snprintf(thread_name, sizeof(thread_name), "Worker-%d", local_thread_num);

printf("线程 %d 启动:\n", local_thread_num);
printf(" TLS thread_id: %d\n", thread_id);
printf(" TLS thread_name: %s\n", thread_name);
printf(" TLS operation_count: %d\n", operation_count);
printf(" 共享 global_counter: %d\n\n", global_counter);

// 模拟工作,增加操作计数
for (int i = 0; i < 5; i++) {
operation_count++;
global_counter++; // 注意:这会导致竞态条件
printf("线程 %s: 操作 %d 完成\n", thread_name, operation_count);
usleep(100000); // 100ms
}

printf("线程 %s 完成,总共执行 %d 次操作\n\n", thread_name, operation_count);
return NULL;
}

int main() {
pthread_t threads&#91;3];
int thread_numbers&#91;3] = {1, 2, 3};

printf("=== 线程局部存储 (TLS) 演示 ===\n\n");

// 主线程的 TLS 值
printf("主线程初始 TLS 值:\n");
printf(" thread_id: %d\n", thread_id);
printf(" thread_name: %s\n", thread_name);
printf(" operation_count: %d\n", operation_count);
printf(" global_counter: %d\n\n", global_counter);

// 设置主线程的 TLS 值
thread_id = 0;
snprintf(thread_name, sizeof(thread_name), "MainThread");

printf("主线程设置后 TLS 值:\n");
printf(" thread_id: %d\n", thread_id);
printf(" thread_name: %s\n", thread_name);
printf(" operation_count: %d\n", operation_count);
printf(" global_counter: %d\n\n", global_counter);

// 创建工作线程
for (int i = 0; i < 3; i++) {
if (pthread_create(&threads&#91;i], NULL, worker_thread, &thread_numbers&#91;i]) != 0) {
perror("pthread_create");
return 1;
}
}

// 等待所有线程完成
for (int i = 0; i < 3; i++) {
pthread_join(threads&#91;i], NULL);
}

printf("所有线程完成后:\n");
printf(" 主线程 thread_id: %d\n", thread_id);
printf(" 主线程 thread_name: %s\n", thread_name);
printf(" 主线程 operation_count: %d\n", operation_count);
printf(" 共享 global_counter: %d\n", global_counter);

printf("\n注意:global_counter 的值可能不准确,因为存在竞态条件\n");
printf("而 TLS 变量的值是正确的,因为每个线程都有独立副本\n");

return 0;
}

示例3:TLS 与错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>

// 线程局部的错误信息
__thread int thread_errno = 0;
__thread char thread_error_msg&#91;256] = "";

// 线程局部的工作状态
__thread struct {
int task_id;
int progress;
char status&#91;64];
} work_state = {0, 0, "初始化"};

// 模拟可能出错的操作
int simulate_operation(int task_id) {
work_state.task_id = task_id;
snprintf(work_state.status, sizeof(work_state.status), "开始任务 %d", task_id);
work_state.progress = 0;

printf("&#91;%s] %s\n", work_state.status, thread_error_msg&#91;0] ? thread_error_msg : "无错误");

// 模拟工作进度
for (int i = 1; i <= 10; i++) {
work_state.progress = i * 10;
snprintf(work_state.status, sizeof(work_state.status), "任务 %d 进度 %d%%", task_id, work_state.progress);

// 模拟随机错误
if (task_id == 2 && i == 7) {
thread_errno = EIO;
snprintf(thread_error_msg, sizeof(thread_error_msg), "I/O 错误发生在任务 %d", task_id);
snprintf(work_state.status, sizeof(work_state.status), "任务 %d 失败", task_id);
printf("&#91;%s] 错误: %s\n", work_state.status, thread_error_msg);
return -1;
}

printf("&#91;%s]\n", work_state.status);
usleep(100000); // 100ms
}

snprintf(work_state.status, sizeof(work_state.status), "任务 %d 完成", task_id);
thread_errno = 0;
thread_error_msg&#91;0] = '\0';
printf("&#91;%s] 完成\n", work_state.status);

return 0;
}

// 线程函数
void* task_thread(void* arg) {
int task_id = *(int*)arg;

printf("=== 线程 %d 开始执行 ===\n", task_id);

// 执行操作
int result = simulate_operation(task_id);

// 报告线程状态
printf("线程 %d 状态报告:\n", task_id);
printf(" 任务 ID: %d\n", work_state.task_id);
printf(" 最终进度: %d%%\n", work_state.progress);
printf(" 最终状态: %s\n", work_state.status);
printf(" 错误码: %d (%s)\n", thread_errno, strerror(thread_errno));
printf(" 错误信息: %s\n", thread_error_msg&#91;0] ? thread_error_msg : "无错误");
printf("\n");

return (void*)(long)result;
}

int main() {
pthread_t threads&#91;3];
int task_ids&#91;3] = {1, 2, 3};
void* results&#91;3];

printf("=== TLS 错误处理演示 ===\n\n");

// 创建线程
for (int i = 0; i < 3; i++) {
if (pthread_create(&threads&#91;i], NULL, task_thread, &task_ids&#91;i]) != 0) {
perror("pthread_create");
return 1;
}
}

// 等待线程完成并收集结果
for (int i = 0; i < 3; i++) {
pthread_join(threads&#91;i], &results&#91;i]);
}

printf("=== 所有任务完成 ===\n");
for (int i = 0; i < 3; i++) {
printf("任务 %d 结果: %s\n", task_ids&#91;i],
(long)results&#91;i] == 0 ? "成功" : "失败");
}

// 主线程的 TLS 状态(应该保持初始值)
printf("\n=== 主线程 TLS 状态 ===\n");
printf("任务 ID: %d\n", work_state.task_id);
printf("进度: %d%%\n", work_state.progress);
printf("状态: %s\n", work_state.status);
printf("错误码: %d\n", thread_errno);
printf("错误信息: %s\n", thread_error_msg&#91;0] ? thread_error_msg : "无错误");

return 0;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
# 编译示例程序
gcc -o tls_example1 example1.c
gcc -o tls_example2 example2.c -lpthread
gcc -o tls_example3 example3.c -lpthread

# 运行示例
./tls_example1
./tls_example2
./tls_example3

现代 TLS 替代方案

使用 C11 thread_local(推荐)

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <threads.h>

// C11 标准的线程局部存储
thread_local int modern_tls_var = 42;

int main() {
printf("C11 thread_local 变量: %d\n", modern_tls_var);
return 0;
}

使用 GCC __thread(广泛支持)

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

// GCC 扩展的线程局部存储
__thread int gcc_tls_var = 100;

int main() {
printf("GCC __thread 变量: %d\n", gcc_tls_var);
return 0;
}

重要注意事项

架构相关: get_thread_area 主要用于 32 位 x86 系统

现代替代: 64 位系统推荐使用 arch_prctl 或标准 TLS

可移植性: 标准的 __thread 或 thread_local 更具可移植性

性能: TLS 访问通常很快,因为使用专门的 CPU 寄存器

初始化: TLS 变量在每个线程中都有独立的初始化值

TLS 的优势

线程安全: 每个线程有独立副本,无需同步

性能好: 访问速度快,无锁开销

使用简单: 像普通变量一样使用

自动管理: 线程结束时自动清理

实际应用场景

错误处理: 每个线程维护独立的错误状态

线程标识: 存储线程特定的 ID 或名称

统计信息: 收集每个线程的性能统计数据

用户数据: 存储线程特定的用户上下文

日志系统: 每个线程维护独立的日志缓冲区

这些示例展示了线程局部存储的概念和使用方法,从底层的 get_thread_area 到现代的 TLS 机制,帮助你理解如何在多线程程序中管理线程特定的数据。

get_thread_area系统调用及示例-CSDN博客