getcpu系统调用及示例

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

1. 函数介绍

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

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

2. 函数原型

#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 节点信息

#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 函数

#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 迁移情况

#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[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 信息

#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[3];
    int thread_ids[3] = {1, 2, 3};
    
    printf("=== 多线程 CPU 信息演示 ===\n");
    
    // 创建多个线程
    for (int i = 0; i < 3; i++) {
        if (pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]) != 0) {
            perror("创建线程失败");
            return 1;
        }
    }
    
    // 等待所有线程完成
    for (int i = 0; i < 3; i++) {
        pthread_join(threads[i], NULL);
    }
    
    printf("所有线程执行完成\n");
    
    return 0;
}

9. NUMA 系统信息查看

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void print_numa_info() {
    FILE *fp;
    char line[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:线程绑定优化

#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:内存分配优化

#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

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

发表回复

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