返回

Linux C语言获取CPU物理核心数 (非逻辑核心)

Linux

Linux下C语言获取物理核心数

有时候,我们需要获取CPU的物理核心数量,而不是逻辑核心数。sysconf(_SC_NPROCESSORS_CONF) 获取到的是逻辑核心数量, 包括了超线程。比如,我的i3处理器,这个函数会返回4,但实际上只有2个物理核心(双核四线程)。怎么用C语言获取物理核心的数量呢?

为什么 sysconf 不行?

sysconf(_SC_NPROCESSORS_CONF)sysconf(_SC_NPROCESSORS_ONLN) 函数获取的是系统配置的处理器数量和当前在线的处理器数量,这些数量包括了超线程带来的逻辑核心。超线程技术让一个物理核心模拟出两个逻辑核心,提高了并行处理能力,但它们共享物理核心的资源。 这两个函数没办法区分物理核心和逻辑核心。

解决方法

要获得物理核心数量,我们需要解析 /proc/cpuinfo 文件或者利用 sysfs 文件系统。下面介绍几种方法:

方法一:解析 /proc/cpuinfo

/proc/cpuinfo 文件包含了CPU的详细信息,我们可以通过解析这个文件来获取物理核心数量。关键是找到 physical idcore id 这两个字段。每个物理核心都有一个唯一的 physical id,同一个物理核心内的所有逻辑核心共享相同的 physical idcore id

原理:

  1. 逐行读取 /proc/cpuinfo
  2. 查找 physical idcore id 字段。
  3. 使用一个数据结构(如数组或哈希表)来记录每个 (physical id, core id) 对。
  4. 不重复的 (physical id, core id) 对的数量就是物理核心数量。

代码示例:

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

int get_physical_cores() {
    FILE *fp;
    char line[256];
    char physical_id[32] = {0};
    char core_id[32] = {0};
    char last_physical_id[32] = {0};
    int physical_cores = 0;
    int core_seen[1024] = {0}; //假设最多有1024个core_id
    int  index=0;

    fp = fopen("/proc/cpuinfo", "r");
    if (fp == NULL) {
        perror("无法打开 /proc/cpuinfo");
        return -1;
    }

    while (fgets(line, sizeof(line), fp) != NULL) {
        if (sscanf(line, "physical id\t: %s", physical_id) == 1) {
             strcpy(last_physical_id,physical_id);
        }

        if (sscanf(line, "core id\t: %s", core_id) == 1) {
              index = atoi(last_physical_id) * 512 + atoi(core_id); //随便给个数组大小就行

             if (core_seen[index] == 0) {
                 core_seen[index] = 1;
                 physical_cores++;
            }
        }
    }

    fclose(fp);
    return physical_cores;
}

int main() {
    int cores = get_physical_cores();
    if (cores != -1) {
        printf("物理核心数量: %d\n", cores);
    }
    return 0;
}

代码解释:

  • fopen 打开 /proc/cpuinfo 文件。
  • fgets 逐行读取文件内容。
  • sscanf 从每一行中解析出 physical idcore id
  • core_seen数组去重,如果physical idcore id第一次出现, 则递增物理核心数量。
  • fclose 关闭文件。

安全建议:

这个方法依赖于 /proc/cpuinfo 文件的格式。 虽然文件格式相对稳定,但未来发生改变也说不定。 使用这个方法需要注意错误处理。

方法二:利用 sysfs 文件系统

/sys/devices/system/cpu 目录下包含有关CPU的信息,比 /proc/cpuinfo 组织得更好。我们可以通过读取这里的文件来获得物理核心数量。

原理:

  1. 遍历 /sys/devices/system/cpu/cpu[0-9]* 目录。
  2. 对于每个 cpu[0-9]* 目录,读取其下的 topology/core_idtopology/physical_package_id 文件。
  3. 使用一个数据结构记录每个 (physical_package_id, core_id) 对。
  4. 不重复的 (physical_package_id, core_id) 对的数量就是物理核心数量。

代码示例:

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

int get_physical_cores_sysfs() {
    DIR *cpu_dir;
    struct dirent *entry;
    int physical_cores = 0;
    int core_seen[1024] = {0}; //假设有最多有1024 (physical_package_id, core_id) 组合
    char path[256];
    char core_id[32];
    char physical_package_id[32];
    FILE *fp;
    int index = 0;

    cpu_dir = opendir("/sys/devices/system/cpu");
    if (cpu_dir == NULL) {
        perror("无法打开 /sys/devices/system/cpu");
        return -1;
    }

    while ((entry = readdir(cpu_dir)) != NULL) {
        if (strncmp(entry->d_name, "cpu", 3) == 0 && entry->d_type == DT_DIR) {

            // 读取 physical_package_id
            snprintf(path, sizeof(path), "/sys/devices/system/cpu/%s/topology/physical_package_id", entry->d_name);
            fp = fopen(path, "r");
            if (fp) {
                if (fgets(physical_package_id, sizeof(physical_package_id), fp) != NULL) {
                    physical_package_id[strcspn(physical_package_id, "\n")] = 0; // 去掉换行符
                 }
                fclose(fp);
             }

            // 读取 core_id
            snprintf(path, sizeof(path), "/sys/devices/system/cpu/%s/topology/core_id", entry->d_name);
           fp = fopen(path, "r");
            if (fp)
            {
                if (fgets(core_id, sizeof(core_id), fp) != NULL) {
                  core_id[strcspn(core_id, "\n")] = 0;// 去掉换行符
                }
              fclose(fp);
            }

            index = atoi(physical_package_id) * 512 + atoi(core_id);  //计算索引值
            if(core_seen[index] == 0)
            {
              core_seen[index] = 1;
              physical_cores++;
            }

        }
    }

    closedir(cpu_dir);
    return physical_cores;
}

int main() {
    int cores = get_physical_cores_sysfs();
    if (cores != -1) {
        printf("物理核心数量 (sysfs): %d\n", cores);
    }
    return 0;
}

代码解释:

  • opendir 打开 /sys/devices/system/cpu 目录。
  • readdir 遍历目录下的条目。
  • strncmp 检查条目名称是否以 "cpu" 开头,并且类型是否是目录(DT_DIR)。
  • snprintf 构建文件路径。
  • fopenfgets 读取 core_idphysical_package_id 文件的内容。
  • core_seen数组去重,如果 physical_package_idcore_id组合是第一次出现,则递增物理核心数量。
  • fclose 关闭文件。
  • closedir 关闭目录。

安全建议:

此方法依赖于 sysfs 文件系统的结构。 与 /proc/cpuinfo 相比,sysfs 文件系统更稳定,推荐使用。

进阶技巧:libcpuid

如果需要获取更详细的CPU信息,例如制造商、型号、缓存大小等,可以考虑使用第三方库,比如 libcpuid。 这个库通过 CPUID 指令来获取CPU信息,更底层。

安装(Ubuntu/Debian): sudo apt-get install libcpuid-dev

安装 (CentOS/RHEL): sudo yum install libcpuid-devel

示例代码 (需要链接 libcpuid):

#include <stdio.h>
#include <libcpuid.h>

int main() {
    if (!cpuid_present()) {
        printf("CPUID 指令不可用.\n");
        return 1;
    }

    struct cpu_raw_data_t raw;
    struct cpu_id_t data;

    if (cpuid_get_raw_data(&raw) < 0) {
        printf("无法获取 CPUID 原始数据: %s\n", cpuid_error());
        return 1;
    }

    if (cpu_identify(&raw, &data) < 0) {
        printf("无法识别 CPU: %s\n", cpuid_error());
        return 1;
    }

    printf("物理核心数量 (libcpuid): %d\n", data.num_cores);
    //还可以获取其他CPU信息.
    //printf("CPU 制造商: %s\n", data.vendor_str);
    //printf("CPU 型号: %s\n", data.brand_str);

    return 0;
}

编译:

gcc -o cpuid_example cpuid_example.c -lcpuid

解释:

  • 引入头文件<libcpuid.h>.
  • cpuid_present() 检测是否支持CPUID指令
  • cpuid_get_raw_data()获取原始数据
  • cpu_identify分析CPUID的数据
  • data.num_cores 成员就表示物理核心数量。
  • 编译需要链接 libcpuid 库。
    使用libcpuid,代码简洁, 且可以直接获取物理核心数。

三种方法都可以帮助我们在 Linux 下用 C 语言获取 CPU 的物理核心数。 可以根据实际情况选择,建议优先用方法二(读取sysfs), 更稳定一些; 方法三 (使用libcpuid)获取信息更丰富。