Contents

LinuxSys:CPU

本文采用知识共享署名 4.0 国际许可协议进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,可适当缩放并在引用处附上图片所在的文章链接。

CPU

本文采用知识共享署名 4.0 国际许可协议进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,可适当缩放并在引用处附上图片所在的文章链接。

多核CPU

多核心处理器(英语:Multi-core processor),又称多核微处理器,是在单个计算组件中加入两个或以上的独立实体中央处理单元(简称核心,英语:Core)。这些核心可以分别独立地运行程序指令,利用并行计算的能力加快程序的运行速度。

通常把两个或更多独立处理器封装在一个单一集成电路(IC)中的方案会称为多核心处理器,而封装在不同IC中的独立处理器形成的计算机系统被称为多处理器。在某些情况中(比如广告中),有些人会将在同一个集成电路中多个独立的单核心微处理器(或多核心微处理器)称做“多处理模块”、“多核心”等,其实是指“多处理器”而不是“多核心处理器”。除非特别说明,本文将使用“多核心”指代在同一集成电路中集成多个独立处理器的CPU(即“多核心处理器”)。

一般情况下,多核心处理器可以在每个核心分别独立物理封装的情况下进行多任务处理(线程级并发处理(Thread-Level Parallelism,TLP),这种形式的TLP通常被认为是芯片级多处理)。

1.物理cpu数:主板上实际插入的cpu数量,可以数不重复的 physical id 有几个(physical id)

2.cpu核数:单块CPU上面能处理数据的芯片组的数量,如双核、四核等 (cpu cores)

3.逻辑cpu数:简单来说,它可使处理器中的1颗内核,如2颗内核那样在操作系统中发挥作用。

这样一来,操作系统可使用的执行资源扩大了一倍,大幅提高了系统的整体性能,此时逻辑cpu=物理CPU个数×每颗核数x2。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 总核数 = 物理CPU个数 X 每颗物理CPU的核数
# 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数

# 查看物理CPU个数
cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l

# 查看每个物理CPU中core的个数(即核数)
cat /proc/cpuinfo| grep "cpu cores"| uniq

# 查看逻辑CPU的个数
cat /proc/cpuinfo| grep "processor"| wc -l

# 查看CPU信息(型号)
cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c

# 查看内 存信息
cat /proc/meminfogit 

uma和numa

CPU 拓扑

每一个 CPU 都会维护这么一个结构体实例,用来描述 CPU 拓扑。

1
2
3
4
5
6
7
struct cpu_topology {
 int thread_id;
 int core_id;
 int cluster_id;
 cpumask_t thread_sibling;
 cpumask_t core_sibling;
};
  • thread_id: 从 mpidr_el1 寄存器中获取
  • core_id:从 mpidr_el1 寄存器中获取
  • cluster_id:从mpidr_el1寄存器中获取
  • thread_sibling:当前 CPU 的兄弟 thread。
  • core_sibling:当前 CPU 的兄弟Core,即在同一个 Cluster 中的 CPU。

可以通过 /sys/devices/system/cpu/cpuX/topology 查看 cpu topology 的信息。

cpu_topology 结构体是通过函数 parse_dt_topology() 解析 DTS 中的信息建立的:

kernel_init() -> kernel_init_freeable() -> smp_prepare_cpus() -> init_cpu_topology() -> parse_dt_topology()

 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
static int __init parse_dt_topology(void)
{
 struct device_node *cn, *map;
 int ret = 0;
 int cpu;

 cn = of_find_node_by_path("/cpus");          ------(1)
 if (!cn) {
  pr_err("No CPU information found in DT\n");
  return 0;
 }

 /*
  * When topology is provided cpu-map is essentially a root
  * cluster with restricted subnodes.
  */
 map = of_get_child_by_name(cn, "cpu-map");   ------(2)
 if (!map)
  goto out;

 ret = parse_cluster(map, 0);                 ------(3)
 if (ret != 0)
  goto out_map;

 topology_normalize_cpu_scale();

 /*
  * Check that all cores are in the topology; the SMP code will
  * only mark cores described in the DT as possible.
  */
 for_each_possible_cpu(cpu)
  if (cpu_topology[cpu].cluster_id == -1)
   ret = -EINVAL;

out_map:
 of_node_put(map);
out:
 of_node_put(cn);
 return ret;
}
  1. 找到 dts 中 cpu topology 的根节点 “/cpus”
  2. 找到 “cpu-map” 节点
  3. 解析 “cpu-map” 中的 cluster

以 rk3399为例,topology 为:”4A53 + 2A72”,dts中定义如下:

 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
	cpus {
		#address-cells = <2>;
		#size-cells = <0>;

		cpu-map {
			cluster0 {
				core0 {
					cpu = <&cpu_l0>;
				};
				core1 {
					cpu = <&cpu_l1>;
				};
				core2 {
					cpu = <&cpu_l2>;
				};
				core3 {
					cpu = <&cpu_l3>;
				};
			};

			cluster1 {
				core0 {
					cpu = <&cpu_b0>;
				};
				core1 {
					cpu = <&cpu_b1>;
				};
			};
		};

		cpu_l0: cpu@0 {
			device_type = "cpu";
			compatible = "arm,cortex-a53", "arm,armv8";
			reg = <0x0 0x0>;
			enable-method = "psci";
			#cooling-cells = <2>; /* min followed by max */
			clocks = <&cru ARMCLKL>;
		};

		cpu_l1: cpu@1 {
			device_type = "cpu";
			compatible = "arm,cortex-a53", "arm,armv8";
			reg = <0x0 0x1>;
			enable-method = "psci";
			clocks = <&cru ARMCLKL>;
		};

		cpu_l2: cpu@2 {
			device_type = "cpu";
			compatible = "arm,cortex-a53", "arm,armv8";
			reg = <0x0 0x2>;
			enable-method = "psci";
			clocks = <&cru ARMCLKL>;
		};

		cpu_l3: cpu@3 {
			device_type = "cpu";
			compatible = "arm,cortex-a53", "arm,armv8";
			reg = <0x0 0x3>;
			enable-method = "psci";
			clocks = <&cru ARMCLKL>;
		};

		cpu_b0: cpu@100 {
			device_type = "cpu";
			compatible = "arm,cortex-a72", "arm,armv8";
			reg = <0x0 0x100>;
			enable-method = "psci";
			#cooling-cells = <2>; /* min followed by max */
			clocks = <&cru ARMCLKB>;
		};

		cpu_b1: cpu@101 {
			device_type = "cpu";
			compatible = "arm,cortex-a72", "arm,armv8";
			reg = <0x0 0x101>;
			enable-method = "psci";
			clocks = <&cru ARMCLKB>;
		};
	};

调度域和调度组

在 Linux 内核中,调度域使用 sched_domain 结构表示,调度组使用 sched_group 结构表示。

调度域 sched_domain

1
2
3
4
5
6
7
8
struct sched_domain {
    struct sched_domain *parent;   
    struct sched_domain *child;     
    struct sched_group  *groups;     
    unsigned long min_interval;
    unsigned long max_interval; 
    ...
};
  • parent:由于调度域是分层的,上层调度域是下层的调度域的父亲,所以这个字段指向的是当前调度域的上层调度域。
  • child:如上所述,这个字段用来指向当前调度域的下层调度域。
  • groups:每个调度域都拥有一批调度组,所以这个字段指向的是属于当前调度域的调度组列表。
  • min_interval/max_interval:做均衡也是需要开销的,不能时刻去检查调度域的均衡状态,这两个参数定义了检查该 sched domain 均衡状态的时间间隔的范围

sched_domain 是分成两个 level,base domain 称为 MC domain(multi core domain),顶层 domain 称为 DIE domain。

调度组 sched_group

1
2
3
4
5
6
7
struct sched_group {
    struct sched_group *next;
    unsigned int group_weight;
    ...
    struct sched_group_capacity *sgc;
    unsigned long cpumask[0];
};
  • next:指向属于同一个调度域的下一个调度组。
  • group_weight:该调度组中有多少个cpu。
  • sgc:该调度组的算力信息。
  • cpumask:用于标记属于当前调度组的 CPU 列表(每个位表示一个 CPU)。

为了减少锁的竞争,每一个 CPU 都有自己的 MC domain、DIE domain 以及 sched_group,并且形成了 sched_domain 之间的层级结构,sched_group 的环形链表结构。CPU 对应的调度域和调度组可通过在设备模型文件 /proc/sys/kernel/sched_domain 里查看。

具体的 sched_domain 的初始化代码分析如下:

kernel_init() -> kernel_init_freeable() -> sched_init_smp() -> init_sched_domains(cpu_active_mask) -> build_sched_domains(doms_cur[0], NULL)

 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
static int
build_sched_domains(const struct cpumask *cpu_map, struct sched_domain_attr *attr)
{
 enum s_alloc alloc_state;
 struct sched_domain *sd;
 struct s_data d;
 int i, ret = -ENOMEM;

 alloc_state = __visit_domain_allocation_hell(&d, cpu_map);    ------(1)
 if (alloc_state != sa_rootdomain)
  goto error;

 /* Set up domains for CPUs specified by the cpu_map: */
 for_each_cpu(i, cpu_map) {
  struct sched_domain_topology_level *tl;

  sd = NULL;
  for_each_sd_topology(tl) {
   sd = build_sched_domain(tl, cpu_map, attr, sd, i);        ------(2)
   if (tl == sched_domain_topology)
    *per_cpu_ptr(d.sd, i) = sd;
   if (tl->flags & SDTL_OVERLAP)
    sd->flags |= SD_OVERLAP;
  }
 }

 /* Build the groups for the domains */
 for_each_cpu(i, cpu_map) {
  for (sd = *per_cpu_ptr(d.sd, i); sd; sd = sd->parent) {
   sd->span_weight = cpumask_weight(sched_domain_span(sd));
   if (sd->flags & SD_OVERLAP) {
    if (build_overlap_sched_groups(sd, i))
     goto error;
   } else {
    if (build_sched_groups(sd, i))                          ------(3)
     goto error;
   }
  }
 }
  ......
 /* Attach the domains */
 rcu_read_lock();
 for_each_cpu(i, cpu_map) {
  int max_cpu = READ_ONCE(d.rd->max_cap_orig_cpu);
  int min_cpu = READ_ONCE(d.rd->min_cap_orig_cpu);

  sd = *per_cpu_ptr(d.sd, i);

  if ((max_cpu < 0) || (cpu_rq(i)->cpu_capacity_orig >
      cpu_rq(max_cpu)->cpu_capacity_orig))
   WRITE_ONCE(d.rd->max_cap_orig_cpu, i);

  if ((min_cpu < 0) || (cpu_rq(i)->cpu_capacity_orig <
      cpu_rq(min_cpu)->cpu_capacity_orig))
   WRITE_ONCE(d.rd->min_cap_orig_cpu, i);

  cpu_attach_domain(sd, d.rd, i);                            ------(4)
 }
 rcu_read_unlock();

 if (!cpumask_empty(cpu_map))
  update_asym_cpucapacity(cpumask_first(cpu_map));

 ret = 0;
error:
 __free_domain_allocs(&d, alloc_state, cpu_map);             ------(5)
 return ret;
}
  1. 在每个 tl 层次,给每个 CPU 分配 sd、sg、sgc 空间
  2. 遍历 cpu_map 里所有 CPU,创建与物理拓扑结构对应的多级调度域
  3. 遍历 cpu_map 里所有 CPU, 创建调度组
  4. 将每个 CPU 的 rq 与 rd(root_domain) 进行绑定
  5. free 掉分配失败或者分配成功多余的内存