• Linux CPU拓扑


    CPU拓扑相关的概念见这篇博客的介绍。这篇笔记重点关注内核部分的代码实现。CPU拓扑的管理是和体系结构相关的,这里我们以ARM64为例进行分析。CPU拓扑管理主要涉及如下几个文件:

    文件

    描述

    arch/arm64/kernel/topology.c

    ARM64的CPU拓扑实现文件,包含了核心的构建CPU拓扑表的流程

    drivers/base/topology.c

    CPU拓扑驱动,通过sysfs向用户态暴露了CPU拓扑结构

    include/linux/topology.h

    体系结构无关的CPU拓扑头文件,提供了默认的CPU拓扑相关接口实现。外部模块总是应该引用该头文件

    include/linux/arch_topology.h

    体系结构相关的CPU拓扑信息

    CPU拓扑表

    ARM平台使用cpu_topology结构来保存CPU拓扑信息,每个CPU一个cpu_topology对象,所有的CPU拓扑信息保存到一个全局数组中。

    1. struct cpu_topology cpu_topology[NR_CPUS];
    2. struct cpu_topology {
    3. int thread_id;
    4. int core_id;
    5. int cluster_id;
    6. cpumask_t thread_sibling;
    7. cpumask_t core_sibling;
    8. };
    • 按照范围从大到小的顺序,cluster_id用来编码系统有多少个socket组成;core_id用来编码socket下的CPU核心;支持超线程技术时,thread_id用来编码同一个CPU核心下的线程(也叫逻辑CPU),ARM不支持超线程,所以thread_id永远是0。
    • thread_siblingcore_sibling是两个CPU掩码。thread_sibling表示属于同一个core下的CPU掩码;core_sibling表示属于同一个socket下所有的所有线程掩码。

    CPU拓扑表会在开机时根据DTS中的配置进行初始化。此外,还提供了一组接口用来根据cpu id查询拓扑表中字段。

    1. #define topology_physical_package_id(cpu) (cpu_topology[cpu].cluster_id)
    2. #define topology_core_id(cpu) (cpu_topology[cpu].core_id)
    3. #define topology_core_cpumask(cpu) (&cpu_topology[cpu].core_sibling)
    4. #define topology_thread_cpumask(cpu) (&cpu_topology[cpu].thread_sibling)

    设置CPU拓扑表

    一个系统的CPU拓扑结构应该是固定的,因此应该在开机初始化时就完成对其设置。开机初始化时,kernel_init()->kernel_init_freeable()->smp_prepare_cpus()->init_cpu_topology()会根据DTS中的cpu_map配置设置内核的cpu_topology[]。其中从smp_prepare_cpus()开始就是体系结构相关的实现。

    1. void __init init_cpu_topology(void)
    2. {
    3. reset_cpu_topology();
    4. if (parse_dt_topology()) // 从DTS中解析cpu拓扑结构
    5. reset_cpu_topology();
    6. }
    7. static int __init parse_dt_topology(void)
    8. {
    9. struct device_node *cn, *map;
    10. int ret = 0;
    11. int cpu;
    12. // 从/cpus/cpu_map节点中解析拓扑
    13. cn = of_find_node_by_path("/cpus");
    14. map = of_get_child_by_name(cn, "cpu-map");
    15. ret = parse_cluster(map, 0); // 解析过程是自顶向下完成的
    16. // 检查确保所有的CPU的拓扑信息都有被解析到
    17. for_each_possible_cpu(cpu)
    18. if (cpu_topology[cpu].cluster_id == -1)
    19. ret = -EINVAL;
    20. ...
    21. return ret;
    22. }

    解析cluster

    1. // 首次调用cluster参数传入的是/cpus/cpu-map
    2. static int __init parse_cluster(struct device_node *cluster, int depth)
    3. {
    4. char name[10];
    5. bool leaf = true;
    6. bool has_cores = false;
    7. struct device_node *c;
    8. static int cluster_id __initdata;
    9. int core_id = 0;
    10. int i, ret;
    11. // 这个循环递归的调用Parse_cluster()函数可以完成/cpus/cpu-map节点下所有cluster节点的解析
    12. i = 0;
    13. do {
    14. snprintf(name, sizeof(name), "cluster%d", i);
    15. c = of_get_child_by_name(cluster, name);
    16. if (c) {
    17. leaf = false;
    18. ret = parse_cluster(c, depth + 1);
    19. of_node_put(c);
    20. if (ret != 0)
    21. return ret;
    22. }
    23. i++;
    24. } while (c); // c为空时,表示当前节点下没有名为clusterXX的子节点,结束循环,尝试解析cluster下的core节点
    25. // 和上面类似,实现解析cluster节点下的所有core节点
    26. i = 0;
    27. do {
    28. snprintf(name, sizeof(name), "core%d", i);
    29. c = of_get_child_by_name(cluster, name);
    30. if (c) {
    31. has_cores = true;
    32. if (depth == 0) { // core节点只能存在cluster节点下,如果直接出现在cpu-map下属于配置错误
    33. pr_err("%s: cpu-map children should be clusters\n",
    34. c->full_name);
    35. of_node_put(c);
    36. return -EINVAL;
    37. }
    38. if (leaf) {
    39. ret = parse_core(c, cluster_id, core_id++);
    40. } else {
    41. pr_err("%s: Non-leaf cluster with core %s\n",
    42. cluster->full_name, name);
    43. ret = -EINVAL;
    44. }
    45. of_node_put(c);
    46. if (ret != 0)
    47. return ret;
    48. }
    49. i++;
    50. } while (c);
    51. if (leaf && !has_cores)
    52. pr_warn("%s: empty cluster\n", cluster->full_name);
    53. if (leaf)
    54. cluster_id++;
    55. return 0;
    56. }

    解析core

    1. static int __init parse_core(struct device_node *core, int cluster_id,
    2. int core_id)
    3. {
    4. char name[10];
    5. bool leaf = true;
    6. int i = 0;
    7. int cpu;
    8. struct device_node *t;
    9. // 如果有配置threadXXX,解析它,支持SMT才会配置
    10. do {
    11. snprintf(name, sizeof(name), "thread%d", i);
    12. t = of_get_child_by_name(core, name);
    13. if (t) {
    14. leaf = false;
    15. cpu = get_cpu_for_node(t);
    16. if (cpu >= 0) {
    17. cpu_topology[cpu].cluster_id = cluster_id;
    18. cpu_topology[cpu].core_id = core_id;
    19. cpu_topology[cpu].thread_id = i;
    20. } else {
    21. pr_err("%s: Can't get CPU for thread\n",
    22. t->full_name);
    23. of_node_put(t);
    24. return -EINVAL;
    25. }
    26. of_node_put(t);
    27. }
    28. i++;
    29. } while (t);
    30. // 根据配置找到对应的CPU ID
    31. cpu = get_cpu_for_node(core);
    32. if (cpu >= 0) {
    33. if (!leaf) {
    34. pr_err("%s: Core has both threads and CPU\n",
    35. core->full_name);
    36. return -EINVAL;
    37. }
    38. cpu_topology[cpu].cluster_id = cluster_id;
    39. cpu_topology[cpu].core_id = core_id;
    40. } else if (leaf) {
    41. pr_err("%s: Can't get CPU for leaf core\n", core->full_name);
    42. return -EINVAL;
    43. }
    44. return 0;
    45. }

    设置mask

    从DTS解析拓扑结构时并未设置thread_sibling和core_sibling。这两个字段是在后面启动完毕后调用store_cpu_topology()函数完成的。

    1. void store_cpu_topology(unsigned int cpuid)
    2. {
    3. struct cpu_topology *cpuid_topo = &cpu_topology[cpuid];
    4. u64 mpidr;
    5. if (cpuid_topo->cluster_id != -1) // DTS有配置时直接根据拓扑表信息计算
    6. goto topology_populated;
    7. // DTS未配置的情况下根据硬件的寄存器设置CPU拓扑表,这里我们不关注
    8. mpidr = read_cpuid_mpidr();
    9. ...
    10. topology_populated:
    11. update_siblings_masks(cpuid);
    12. }
    13. static void update_siblings_masks(unsigned int cpuid)
    14. {
    15. struct cpu_topology *cpu_topo, *cpuid_topo = &cpu_topology[cpuid];
    16. int cpu;
    17. /* update core and thread sibling masks */
    18. for_each_possible_cpu(cpu) {
    19. cpu_topo = &cpu_topology[cpu];
    20. // 同一个cluster下的CPU为core_cibling
    21. if (cpuid_topo->cluster_id != cpu_topo->cluster_id)
    22. continue;
    23. cpumask_set_cpu(cpuid, &cpu_topo->core_sibling);
    24. if (cpu != cpuid)
    25. cpumask_set_cpu(cpu, &cpuid_topo->core_sibling);
    26. // 同一个core下面的CPU为thread_sibling
    27. if (cpuid_topo->core_id != cpu_topo->core_id)
    28. continue;
    29. cpumask_set_cpu(cpuid, &cpu_topo->thread_sibling);
    30. if (cpu != cpuid)
    31. cpumask_set_cpu(cpu, &cpuid_topo->thread_sibling);
    32. }
    33. }

    sysfs接口

    用户态可以通过/sys/devices/system/cpu/cpuX/topology目录下的内容来获取到整个系统的CPU拓扑信息。下面是一个2大核+6小核手机上的各cpu拓扑信息值:

    cpu

    physical_package_id

    core_id

    core_siblings

    core_siblings_list

    thread_siblings

    thread_siblings_list

    0

    0

    0

    3F

    0-5

    01

    0

    1

    0

    1

    3F

    0-5

    02

    1

    2

    0

    2

    3F

    0-5

    04

    2

    3

    0

    3

    3F

    0-5

    08

    3

    4

    0

    4

    3F

    0-5

    10

    4

    5

    0

    5

    3F

    0-5

    20

    5

    6

    0

    0

    C0

    6-7

    40

    6

    7

    0

    1

    C0

    6-7

    80

    7

  • 相关阅读:
    POST请求的请求参数中文乱码问题 [JavaWeb][Servlet]
    [附源码]java毕业设计面试刷题系统
    oracle数据库导入导出
    【java毕业设计】基于java+SSH+jsp的文章发布系统设计与实现(毕业论文+程序源码)——文章发布系统
    Go 快速开发朋友圈助力项目
    计算机毕业设计Java网上作业提交与批改系统(源码+系统+mysql数据库+Lw文档)
    SpringCloud系列(13)--Eureka服务名称修改和服务IP显示
    学习笔记15--驾驶舒适度评价体系
    玩转“产业生态”,长城汽车森林生态很“未来”
    FixedWindowRollingPolicy简介说明
  • 原文地址:https://blog.csdn.net/fanxiaoyu321/article/details/134453093