getcpu系统调用及示例

getcpu - 获取当前 CPU 和 NUMA 节点信息]()

1. 函数介绍

getcpu 是一个 Linux 系统调用,用于获取当前线程正在运行的 CPU 核心编号和 NUMA(Non-Uniform Memory Access)节点编号。这个函数对于性能分析、负载均衡、线程亲和性设置等场景非常有用。

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

NUMA 是一种多处理器计算机内存设计,其中内存访问时间取决于内存相对于处理器的位置。了解当前线程在哪个 CPU 核心和 NUMA 节点上运行,有助于优化程序性能。

2. 函数原型

1
2
3
4
5
6
7
#define _GNU_SOURCE
#include <linux/getcpu.h>
#include <sys/syscall.h>
#include <unistd.h>

long getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *tcache);

注意:该函数不是标准 C 库的一部分,需要通过 syscall() 调用或者包含 glibc 2.29+ 版本后才能直接调用。

3. 功能

获取当前线程正在运行的 CPU 核心编号和 NUMA 节点编号。这是一个轻量级的操作,通常用于性能监控和调度优化。

4. 参数

unsigned *cpu: 指向存储 CPU 核心编号的变量的指针

  • 如果为 NULL:不返回 CPU 信息

  • 如果非 NULL:存储当前 CPU 核心编号(从 0 开始)

unsigned *node: 指向存储 NUMA 节点编号的变量的指针

  • 如果为 NULL:不返回 NUMA 节点信息

  • 如果非 NULL:存储当前 NUMA 节点编号(从 0 开始)

struct getcpu_cache *tcache: 缓存结构体指针,用于优化性能

  • 通常传入 NULL(内核会使用默认缓存机制)

  • 主要供内核内部使用

5. 返回值

  • 成功时返回 0

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

6. 常见 errno 错误码

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

  • EINVAL: 无效参数

  • ENOSYS: 系统不支持该功能

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

  • sched_getcpu(): glibc 提供的获取当前 CPU 的便捷函数

  • sched_setaffinity(): 设置进程 CPU 亲和性

  • sched_getaffinity(): 获取进程 CPU 亲和性

  • pthread_setaffinity_np(): 设置线程 CPU 亲和性

  • pthread_getaffinity_np(): 获取线程 CPU 亲和性

  • /proc/cpuinfo: 查看 CPU 信息

  • /sys/devices/system/cpu/: CPU 拓扑信息

  • numactl: NUMA 控制工具

8. 示例代码

示例1:基本使用 - 获取当前 CPU 和 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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/getcpu.h>
#include <errno.h>
#include <string.h>

#ifndef SYS_getcpu
# define SYS_getcpu 309 // x86_64 架构下的系统调用号
#endif

int main() {
unsigned cpu, node;
long result;

printf("=== 获取当前 CPU 和 NUMA 节点信息 ===\n");

// 使用 syscall 调用 getcpu
result = syscall(SYS_getcpu, &cpu, &node, NULL);

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

printf("当前线程运行信息:\n");
printf(" CPU 核心编号: %u\n", cpu);
printf(" NUMA 节点编号: %u\n", node);

// 验证系统 CPU 数量
long nprocs = sysconf(_SC_NPROCESSORS_ONLN);
printf(" 系统在线 CPU 数: %ld\n", nprocs);

if (cpu >= nprocs) {
printf(" 警告: CPU 编号超出系统 CPU 数范围\n");
}

return 0;
}

示例2:使用 glibc 提供的 sched_getcpu 函数

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <errno.h>
#include <string.h>

int main() {
int cpu;

printf("=== 使用 sched_getcpu 获取 CPU 信息 ===\n");

cpu = sched_getcpu();
if (cpu == -1) {
perror("sched_getcpu 调用失败");
return 1;
}

printf("当前 CPU 核心编号: %d\n", cpu);

// 获取系统信息进行验证
long nprocs_conf = sysconf(_SC_NPROCESSORS_CONF);
long nprocs_onln = sysconf(_SC_NPROCESSORS_ONLN);

printf("系统配置 CPU 数: %ld\n", nprocs_conf);
printf("系统在线 CPU 数: %ld\n", nprocs_onln);

if (cpu >= nprocs_onln) {
printf("警告: CPU 编号超出在线 CPU 范围\n");
}

return 0;
}

示例3:监控 CPU 迁移情况

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/getcpu.h>
#include <sched.h>
#include <time.h>

#ifndef SYS_getcpu
# define SYS_getcpu 309
#endif

void monitor_cpu_migration(int duration_seconds) {
unsigned cpu, node;
unsigned last_cpu = (unsigned)-1;
unsigned last_node = (unsigned)-1;
int migrations = 0;
time_t start_time = time(NULL);
time_t current_time;

printf("开始监控 CPU 迁移情况 (%d 秒)...\n", duration_seconds);
printf("%-10s %-6s %-6s %-15s\n", "时间", "CPU", "Node", "状态");
printf("%-10s %-6s %-6s %-15s\n", "----", "---", "----", "----");

while ((current_time = time(NULL)) - start_time < duration_seconds) {
if (syscall(SYS_getcpu, &cpu, &node, NULL) == 0) {
char status&#91;20] = "运行中";

if (last_cpu != (unsigned)-1) {
if (cpu != last_cpu) {
snprintf(status, sizeof(status), "CPU迁移 %u->%u", last_cpu, cpu);
migrations++;
} else if (node != last_node) {
snprintf(status, sizeof(status), "Node迁移 %u->%u", last_node, node);
migrations++;
}
}

printf("%-10ld %-6u %-6u %-15s\n",
current_time - start_time, cpu, node, status);

last_cpu = cpu;
last_node = node;
}

usleep(500000); // 休眠 0.5 秒
}

printf("\n监控结束:\n");
printf(" 总迁移次数: %d\n", migrations);
printf(" 平均迁移间隔: %.2f 秒\n",
migrations > 0 ? (double)duration_seconds / migrations : 0.0);
}

int main() {
printf("=== CPU 迁移监控演示 ===\n");

// 获取初始信息
unsigned cpu, node;
if (syscall(SYS_getcpu, &cpu, &node, NULL) == 0) {
printf("初始位置: CPU %u, Node %u\n", cpu, node);
}

// 监控 10 秒钟的 CPU 迁移情况
monitor_cpu_migration(10);

return 0;
}

示例4:多线程环境下的 CPU 信息

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <linux/getcpu.h>
#include <sched.h>

#ifndef SYS_getcpu
# define SYS_getcpu 309
#endif

void* thread_function(void *arg) {
int thread_id = *(int*)arg;
unsigned cpu, node;
cpu_set_t cpuset;
int ret;

// 获取当前线程的 CPU 信息
if (syscall(SYS_getcpu, &cpu, &node, NULL) == 0) {
printf("线程 %d: 初始位置 CPU %u, Node %u\n", thread_id, cpu, node);
}

// 获取当前 CPU 亲和性
ret = pthread_getaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
if (ret == 0) {
printf("线程 %d: 可运行在 CPU ", thread_id);
for (int i = 0; i < CPU_SETSIZE; i++) {
if (CPU_ISSET(i, &cpuset)) {
printf("%d ", i);
}
}
printf("\n");
}

// 执行一些工作
for (int i = 0; i < 5; i++) {
sleep(1);
if (syscall(SYS_getcpu, &cpu, &node, NULL) == 0) {
printf("线程 %d: 当前位置 CPU %u, Node %u\n", thread_id, cpu, node);
}
}

return NULL;
}

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

printf("=== 多线程 CPU 信息演示 ===\n");

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

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

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

return 0;
}

9. 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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void print_numa_info() {
FILE *fp;
char line&#91;256];

printf("\n=== NUMA 系统信息 ===\n");

// 检查 NUMA 支持
if (access("/proc/numa_maps", F_OK) == 0) {
printf("系统支持 NUMA\n");
} else {
printf("系统不支持 NUMA 或无访问权限\n");
return;
}

// 查看 NUMA 节点信息
fp = fopen("/sys/devices/system/node/online", "r");
if (fp) {
if (fgets(line, sizeof(line), fp)) {
printf("在线 NUMA 节点: %s", line);
}
fclose(fp);
}

// 查看 CPU 拓扑
system("lscpu | grep -E 'NUMA|CPU(s)'");
}

10. 性能优化应用场景

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

场景1:线程绑定优化

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

void bind_thread_to_cpu(int target_cpu) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(target_cpu, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
}

场景2:内存分配优化

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

void* allocate_local_memory(size_t size) {
if (numa_available() == -1) {
return malloc(size); // NUMA 不可用,使用普通分配
}
return numa_alloc_local(size); // 分配在本地 NUMA 节点
}

11. 实际系统中的应用

  1. 数据库系统: 将查询线程绑定到特定 CPU 以提高缓存命中率2. Web 服务器: 均衡分配请求处理线程到不同 CPU 核心3. 高性能计算: 优化 MPI 进程的 CPU 和 NUMA 布局4. 实时系统: 确保关键任务在指定 CPU 上运行5. 容器技术: Docker 和 Kubernetes 中的 CPU 资源管理

总结

getcpu 是一个重要的系统调用,用于获取当前线程的 CPU 和 NUMA 节点信息。关键要点:

  1. 轻量级操作: 快速获取当前执行环境信息2. NUMA 感知: 支持现代多核 NUMA 系统3. 性能优化: 为调度和内存访问优化提供基础信息4. 多线程支持: 在并发环境中准确反映线程位置5. 系统监控: 用于负载均衡和性能分析工具

在编写高性能应用程序时,了解和利用 CPU 和 NUMA 信息可以显著提升程序性能,特别是在多核和 NUMA 系统上。】

CSDN-LINK:https://blog.csdn.net/timberwolf007/article/details/150473820?sharetype=blogdetail&sharerId=150473820&sharerefer=PC&sharesource=zidier215&spm=1011.2480.3001.8118

data-ad-format="auto" data-full-width-responsive="true">