多处理器系统(Multi-Processor Systems,MPS),是指一个含有多个处理器的计算机系统。MPS需要硬件和软件的同时支持。在硬件上,根据多处理器之间的相互联系程序和工作特点,MPS又可以简单地分成松耦合多处理器系统、对称多处理器(Symmetric Multi-Processor,SMP)系统和非对称多处理器(Asymmetric Multi-Processor,ASMP)系统。
在SMP系统中,存储器、磁盘I/O等系统主要资源被系统中的所有处理器共享,工作负载被均匀地分配到所有可用的处理器上,这就避免了系统在执行某些特定任务时一些处理器忙不过来,而另一些处理器却闲着的性能浪费弊病,因为当系统增加了一个处理器时,系统就会重新分配正在执行的任务,将其他处理器上的部分任务调剂到这颗处理器上,使系统执行的所有任务,由所有处理器共同均担。SMP系统具有很强的负载均衡与调节能力,不仅可保证计算系统的整体性能得到充分提高,在可靠性和稳定性方面也使得系统得到了极大改善。
T2 CPU有8个物理核,每个核上有8个strand,也就有64个虚拟处理器。显然T2也是一个SMP系统。
本章的内容是Linux 对T2 CPU的SMP的支持的体系结构相关部分代码的分析。本章基本不涉及Linux SMP支持体系结构相关部分的实现。因此通过本章你可能无法获得Linux SMP支持的总体设计思想及实现,它们并不是本章的目的。
本章主要包括3个部分的代码的讲解:
1,T2 Linux SMP的初始化:主要内容是系统启动时如何通过boot cpu启动其他所有的cpu。
2,T2 Linux cpu的分配:T2 CPU上有64个虚拟处理器,这64个虚拟处理器并不是完全相互独立的,在任意时刻T2的每个物理核上的8个strand中只能有两个同时处于工作状态,因为T2的每个物理核上只有两个执行单元,而这两个执行单元是由8个strand所共享的。为了提高T2的并发性,一个很显然的策略是为任务分配cpu时,尽可能分配在能够同时执行的cpu上,T2 linux实现自己的cpu分配策略。
3,T2 Linux处理器间中断:一个cpu在执行的过程中可能需要与其它的cpu交互,比如一个cpu可能需要通知另一个cpu以让那个cpu执行调度程序,再比如当一个cpu修改了一个tlb表项时,需要通知其他cpu以让其他cpu的tlb中的相同表项失效。不同cpu间的交互是通过处理器间中断来实现的,本章的处理器间中断的讲解是基于讲解Linux T2 中断机制详解那一章的讲解之上的,阅读本章时,你可能需要参考Linux T2 中断机制详解那一章的内容。
T2 Linux SMP的初始化:
 在初始化SMP之前需要先初始化per_cpu域,per_cpu域是每个cpu都有的一个域,对于同一个per_cpu变量,在每个per_cpu域中都有一个相互独立的副本,每个cpu在访问per_cpu变量时实际上是访问各自per_cpu域中的副本,这样cpu在访问per_cpu变量时就不会产生竞争从而提供系统并发执行的效率。在Linux的内核镜像中有一个.data.percpu段,这个段里面存放着Linux中静态定义的per_cpu变量,per_cpu域的初始化实际上是为每个cpu的per_cpu域分配空间,然后把.data.percpu段的内容在么个per_cpu域中都赋值一份,同时还会初始化一个per_cpu管理机制以及一个per_cpu内存分配器以在内核运行的过程中动态分配per_cpu变量。
 对于per_cpu域的初始化我们并不打算详细讲解,只关注其中对后续SMP的初始化理解有帮助的部分,per_cpu域的初始化是在start_kernel()–>setup_per_cpu_areas中完成的,在T2 linux中,该函数定义在arch/sparc/kernel/smp_64.c文件中,代码如下:
1456 void __init setup_per_cpu_areas(void)
1457 {
1458 unsigned long delta;
1459 unsigned int cpu;
1460 int rc = -EINVAL;
1461
1462 if (pcpu_chosen_fc != PCPU_FC_PAGE) {
1463 rc = pcpu_embed_first_chunk(PERCPU_MODULE_RESERVE,
1464 PERCPU_DYNAMIC_RESERVE, 4 << 20,
1465 pcpu_cpu_distance,
1466 pcpu_alloc_bootmem,
1467 pcpu_free_bootmem);
1468 if (rc)
1469 pr_warning("PERCPU: %s allocator failed (%d), "
1470 "falling back to page size\n",
1471 pcpu_fc_names[pcpu_chosen_fc], rc);
1472 }
1473 if (rc < 0)
1474 rc = pcpu_page_first_chunk(PERCPU_MODULE_RESERVE,
1475 pcpu_alloc_bootmem,
1476 pcpu_free_bootmem,
1477 pcpu_populate_pte);
1478 if (rc < 0)
1479 panic("cannot initialize percpu area (err=%d)", rc);
1480
1481 delta = (unsigned long)pcpu_base_addr - (unsigned long)__per_cpu_start;
1482 for_each_possible_cpu(cpu)
1483 __per_cpu_offset(cpu) = delta + pcpu_unit_offsets[cpu];
1484
1485 /* Setup %g5 for the boot cpu. */
1486 __local_per_cpu_offset = __per_cpu_offset(smp_processor_id());
1487
1488 of_fill_in_cpu_data();
1489 if (tlb_type == hypervisor)
1490 mdesc_fill_in_cpu_data(cpu_all_mask);
1491 }
第1462到1479行的分支语句我们之后执行第1474行的分支语句, pcpu_page_first_chunk函数实际上就已经初始化好了per_cpu域,这个函数我们不细讲。
 第1481行,计算出分配的per_cpu域的空间与.data.percpu段之间的偏移,实际上这两个内存段在内存中是相邻的
第1482,1483行,对于系统中的每一个cpu,计算出他们各自的percpu域相对与.data.percpu段的偏移,保存到__per_cpu_offset(cpu) 中,实际上__per_cpu_offset(cpu)就是每个cpu的trap_block变量中的成员__per_cpu_base,这里把该偏移保存到trap_block变量中。这个偏移主要是在内核执行中动态分配per_cpu变量时会使用到。
第1486行,__local_per_cpu_offset变量是一个寄存器变量,它实际上就是寄存器%g5,每个cpu上都会有寄存器g5,因此__local_per_cpu_offset变量也可以看作一个per_cpu变量。在T2 linux中每个cpu的%g5寄存器默认指向每个cpu的per_cpu域的偏移。
第1488行,该函数在我们这里什么也不做。
第1490行,mdesc_fill_in_cpu_data函数在分析setup_arch函数时见过,当时说在初始化完per_cpu变量后该函数会执行,就是要在这里执行了,我们看这个函数:
869 void __cpuinit mdesc_fill_in_cpu_data(cpumask_t *mask)
870 {
871 struct mdesc_handle *hp;
872
873 mdesc_iterate_over_cpus(fill_in_one_cpu, NULL, mask);
874
875 #ifdef CONFIG_SMP
876 sparc64_multi_core = 1;
877 #endif
878
879 hp = mdesc_grab();
880
881 set_core_ids(hp);
882 set_proc_ids(hp);
883
884 mdesc_release(hp);
885
886 smp_fill_in_sib_core_maps();
887 }
第873行, mdesc_iterate_over_cpus函数刚才分析过了,刚才为每个CPU执行的是record_one_cpu函数,并且刚刚的目的是为了设置cpu位图。而这里执行的是 fill_in_one_cpu函数,当然这里也不是设置cpu位图了,这里干什么呢,我们看看 fill_in_one_cpu函数:
816 static void * __cpuinit fill_in_one_cpu(struct mdesc_handle *hp, u64 mp, int cpuid, void *arg)
817 {
818 const u64 *cfreq = mdesc_get_property(hp, mp, "clock-frequency", NULL);
819 struct trap_per_cpu *tb;
820 cpuinfo_sparc *c;
821 u64 a;
822
823 #ifndef CONFIG_SMP
824 /* On uniprocessor we only want the values for the
825 * real physical cpu the kernel booted onto, however
826 * cpu_data() only has one entry at index 0.
827 */
828 if (cpuid != real_hard_smp_processor_id())
829 return NULL;
830 cpuid = 0;
831 #endif
832
833 c = &cpu_data(cpuid);
834 c->clock_tick = *cfreq;
835 if (!c->clock_tick) {
836 sun4v_get_frequency(0, &a);
837 c->clock_tick = a;
838 }
839
840 tb = &trap_block[cpuid];
841 get_mondo_data(hp, mp, tb);
842
843 mdesc_for_each_arc(a, hp, mp, MDESC_ARC_TYPE_FWD) {
844 u64 j, t = mdesc_arc_target(hp, a);
845 const char *t_name;
846
847 t_name = mdesc_node_name(hp, t);
848 if (!strcmp(t_name, "cache")) {
849 fill_in_one_cache(c, hp, t);
850 continue;
851 }
852
853 mdesc_for_each_arc(j, hp, t, MDESC_ARC_TYPE_FWD) {
854 u64 n = mdesc_arc_target(hp, j);
855 const char *n_name;
856
857 n_name = mdesc_node_name(hp, n);
858 if (!strcmp(n_name, "cache"))
859 fill_in_one_cache(c, hp, n);
860 }
861 }
862
863 c->core_id = 0;
864 c->proc_id = -1;
865
866 return NULL;
867 }
第818行,得到cpu 节点的"clock-frequency" property,即标志了该cpu的频率。
第823到831行,我们是SMP架构的,所以这几条语句不执行。
第833行,cpu_data是一个宏,通过该宏可以得到当前cpu的percpu变量cpuinfo_sparc,每个cpu的cpuinfo_sparc的变量存放了该cpu的一些基本信息,它们定义于文件arch/sparc/include/asm/cpudata_64.h中:
typedef struct {
/* Dcache line 1 */
unsigned int __softirq_pending; /* must be 1st, see rtrap.S */
unsigned int __nmi_count;
unsigned long clock_tick; /* %tick's per second */
unsigned long __pad;
unsigned int __pad1;
unsigned int __pad2;
/* Dcache line 2, rarely used */
unsigned int dcache_size;
unsigned int dcache_line_size;
unsigned int icache_size;
unsigned int icache_line_size;
unsigned int ecache_size;
unsigned int ecache_line_size;
int core_id;
int proc_id;
} cpuinfo_sparc;
DECLARE_PER_CPU(cpuinfo_sparc, __cpu_data);
#define cpu_data(__cpu) per_cpu(__cpu_data, (__cpu))
第834行,把得到的cpu频率存放在cpuinfo_sparc中
第840行,得到当前cpu的trap block变量,
第841行,这个函数就不详细分析了,它通过cpu节点的property得到当前cpu的4个中断mondo队列的长度,通过该长度初始化当前cpu的trap_block结构的cpu_mondo_qmask, dev_mondo_qmask,resum_qmask,nonresum_qmask成员,在中断处理中可以看到这些成员的使用。
第843行到861行,这个循环里面的内容也不详细分析了,有兴趣的话可以自行分析,他实际上就是通过cpu节点的arc属性找到与这个虚拟cpu相关联的cache节点,包括指令cache,数据cache等,然后通过cache节点的属性得到cache的大小,cahce line大小等资源信息保存到每个虚拟cpu的per_cpu变量cpuinfo_sparc中。在machine description中每个节点都有多个arc属性,这些arc属性指向与该节点相关联的其他节点,因此通过cpu节点的arc属性可以找到与该cpu相关联cache节点。
mdesc_fill_in_cpu_data函数就是通过machine description的信息初始化cpu的两个管理结构trap_block和cpuinfo_sparc。
回到mdesc_fill_in_cpu_data函数中来,
第876行,sparc64_multi_core变量初始化为1。
第879行,mdesc_grab函数前面也提到过,他是得到当前的machine description,由此可见后面会用machine description机制得到系统资源信息。
第881行,set_core_ids实际上是通过machine description得到每个cpu的core_id,每个T2 CPU上有8个物理处理器核,而每个虚拟处理器都属于其中的一个物理核。我们来看这个函数:
660 static void __cpuinit set_core_ids(struct mdesc_handle *hp)
661 {
662 int idx;
663 u64 mp;
664
665 idx = 1;
666 mdesc_for_each_node_by_name(hp, mp, "cache") {
667 const u64 *level;
668 const char *type;
669 int len;
670
671 level = mdesc_get_property(hp, mp, "level", NULL);
672 if (*level != 1)
673 continue;
674
675 type = mdesc_get_property(hp, mp, "type", &len);
676 if (!of_find_in_proplist(type, "instn", len))
677 continue;
678
679 mark_core_ids(hp, mp, idx);
680
681 idx++;
682 }
683 }
set_core_ids函数会找到每个物理处理器核,并且根据找到的顺序为物理处理器核分配id号。怎么找到物理处理器核呢?在T2 CPU中每个物理核都有一个一级指令cache和数据cache,这两个cache为同一物理核上的所有strand所共享,而T2 CPU上的8个物理核共享一个二级cache。所有的cache在machine description里都有一代表它的节点。因为每一个物理核上都有一个唯一的一级数据cache,因此每找到一个一级数据cache,就代表找到了一个物理核。
第666行,遍历machine description中的所有cache节点。
第671行,通过cache节点的level属性判断该节点代表一级cache还是二级cache,如果是二级cache,显然不是我们需要的,推出进行下次循环。
第675行,通过cache节点的type属性。
第676行,of_find_in_proplist函数实际上是个字符串比较函数,把type属性与"instn"比较来判断该节点是指令cache还是数据cache,我们只找数据cache。
第679行,每找到一个物理处理器核,则计数器idx即为为该物理处理器核分配的id号,然后以此为参数调用mark_core_ids函数
mark_core_ids函数的作用是把该物理处理器上的所有虚拟cpu的per_cpu变量cpuinfo_sparc的core_id字段设置为该物理处理器核的id。当然要做到这一点首先就要找到该物理处理器核上的所有虚拟cpu的cpuid,这也是通过查找machine description来实现的。在machine description中,每个节点都有一个或多个arc属性,这些arc属性指向与该节点相关联的其他节点,比如一级数据cache节点的一些arc属性可能指向共享该cache的虚拟cpu,通过arc属性就可以找到该cpu节点,然后得到cpu节点的id属性,从而得到其cpuid。mark_core_ids函数就不一行行分析了,它的作用这里已经说的很清楚了,如果感兴趣的话可以自行分析。
到这里set_core_ids函数就分析完了,它的作用是为系统中每一个虚拟cpu,找到其所属的物理处理器核,然后把物理处理器核id保存到每个虚拟cpu的per_cpu变量cpuinfo_sparc的core_id字段中。
我们回到mdesc_fill_in_cpu_data函数中来,
第882行,set_proc_ids函数也不一行行分析了,它的作用与set_core_ids函数类似,不同的是set_core_ids设置的是每个虚拟cpu的物理处理器核id,而这里设置的是每个虚拟cpu所属的线程组的id,保存到每个虚拟cpu的per_cpu变量cpuinfo_sparc的porc_id字段中。T2 cpu中每个物理处理器核上有8个strand,这8个strand分为两个线程组,每个线程组中有4个虚拟cpu,他们共享一个整型执行单元,因此整个T2 CPU共有16个线程组。
第884行,mdesc_release函数释放linux中的当前machine description。当使用完machine description后释放其空间。
第886行,smp_fill_in_sib_core_maps函数会根据每个cpu的core_id和proc_id为每个虚拟cpu设置两个位图,我们来看该函数:
1195 void __devinit smp_fill_in_sib_core_maps(void)
1196 {
1197 unsigned int i;
1198
1199 for_each_present_cpu(i) {
1200 unsigned int j;
1201
1202 cpus_clear(cpu_core_map[i]);
1203 if (cpu_data(i).core_id == 0) {
1204 cpu_set(i, cpu_core_map[i]);
1205 continue;
1206 }
1207
1208 for_each_present_cpu(j) {
1209 if (cpu_data(i).core_id ==
1210 cpu_data(j).core_id)
1211 cpu_set(j, cpu_core_map[i]);
1212 }
1213 }
1214
1215 for_each_present_cpu(i) {
1216 unsigned int j;
1217
1218 cpus_clear(per_cpu(cpu_sibling_map, i));
1219 if (cpu_data(i).proc_id == -1) {
1220 cpu_set(i, per_cpu(cpu_sibling_map, i));
1221 continue;
1222 }
1223
1224 for_each_present_cpu(j) {
1225 if (cpu_data(i).proc_id ==
1226 cpu_data(j).proc_id)
1227 cpu_set(j, per_cpu(cpu_sibling_map, i));
1228 }
1229 }
1230 }
第1199到1213行,这个for循环是为每个cpu虚拟设置cpu_core_map位图,从这个位图可以得到所有与该虚拟cpu同属于同一物理处理器核的的虚拟cpu,显然它会判断所有的虚拟cpu的per_cpu变量sparc_cpuinfo的core_id字段,如果预期自己的相等,就在该位图中置位。
第1215到1229行,这个循环与前面的循环一样,不过这里是为每个虚拟cpu设置proc_id位图。
到这里mdesc_fill_in_cpu_data函数就分析完了,它的作用主要是为每个虚拟cpu初始化per_cpu变量sparc_cpuinfo和trap_block变量,并同时设置了物理处理器核位图和线程组位图。
setup_per_cpu_areas函数也就分析完了,它初始化了per_cpu域,这一点sparc和其它的体系结构是一样的,然后初始化了cpu相关信息。
per_cpu域初始化完成后就可以正式开始SMP初始化了。
Linux SMP初始化是按照如下顺序初始化的:
start_kernel()->rest_init()->kernel_init()->smp_init()
其中smp_init正式开始SMP初始化。smp_init函数是体系结构无关函数,所以该函数就不贴出来分析了,它会为系统中每一个present cpu调用cpu_up函数从而启动该cpu,在此之前,系统中一直只有一个cpu(boot cpu)处于运行状态,这里是使其他的cpu处于运行状态,在这个过程中会为这些cpu建立执行环境。
cpu_up函数也是体系结构无关函数,它做一些判断,然后调用_cpu_up函数真正开始启动cpu的工作。
_ cpu_up函数是体系结构相关函数,它定义于arch/sparc/kernel/smp_64.c文件中:
1232 int __cpuinit __cpu_up(unsigned int cpu)
1233 {
1234 int ret = smp_boot_one_cpu(cpu);
1235
1236 if (!ret) {
1237 cpu_set(cpu, smp_commenced_mask);
1238 while (!cpu_isset(cpu, cpu_online_map))
1239 mb();
1240 if (!cpu_isset(cpu, cpu_online_map)) {
1241 ret = -ENODEV;
1242 } else {
1243 /* On SUN4V, writes to %tick and %stick are
1244 * not allowed.
1245 */
1246 if (tlb_type != hypervisor)
1247 smp_synchronize_one_tick(cpu);
1248 }
1249 }
1250 return ret;
1251 }
第1234行, smp_boot_one_cpu函数是正式启动一个cpu,启动一个cpu除了为其建立执行环境外,可能还需要初始化一些Linux的一些子系统数据结构,初始化Linux的子系统(比如内存管理,设备资源等)及其数据结构在boot cpu启动的过程中绝大部分已经完成了,这些工作其他的cpu就不需要再做了,但是有些特殊的情况可能每个cpu都单独的初始化,这种初始化就是在启动一个cpu的过程中执行的,我们来看smp_boot_one_cpu函数:
345 static int __cpuinit smp_boot_one_cpu(unsigned int cpu)
346 {
347 unsigned long entry =
348 (unsigned long)(&sparc64_cpu_startup);
349 unsigned long cookie =
350 (unsigned long)(&cpu_new_thread);
351 struct task_struct *p;
352 void *descr = NULL;
353 int timeout, ret;
354
355 p = fork_idle(cpu);
356 if (IS_ERR(p))
357 return PTR_ERR(p);
358 callin_flag = 0;
359 cpu_new_thread = task_thread_info(p);
360
361 if (tlb_type == hypervisor) {
362 #if defined(CONFIG_SUN_LDOMS) && defined(CONFIG_HOTPLUG_CPU)
363 if (ldom_domaining_enabled)
364 ldom_startcpu_cpuid(cpu,
365 (unsigned long) cpu_new_thread,
366 &descr);
367 else
368 #endif
369 prom_startcpu_cpuid(cpu, entry, cookie);
370 } else {
371 struct device_node *dp = of_find_node_by_cpuid(cpu);
372
373 prom_startcpu(dp->node, entry, cookie);
374 }
375
376 for (timeout = 0; timeout < 50000; timeout++) {
377 if (callin_flag)
378 break;
379 udelay(100);
380 }
381
382 if (callin_flag) {
383 ret = 0;
384 } else {
385 printk("Processor %d is stuck.\n", cpu);
386 ret = -ENODEV;
387 }
388 cpu_new_thread = NULL;
389
390 kfree(descr);
391
392 return ret;
393 }
第347,348行, sparc64_cpu_startup变量是在arch/sparckernel/trampoline_64.S文件中定义的,它实际上是一段汇编代码的入口,这段汇编代码会执行cpu启动的工作。
第340,350行,cpu_new_thread变量是struct thread_info *类型的变量,在Linux下cpu执行环境是以进程为单位的,因此启动cpu是肯定会为cpu运行一个进程,cpu_new_thread则指向进程的内核栈。
第355行, fork_idle会创建一个新的进程,这个进程是从init进程复制来的,该进程实际上就是传说中的idle进程,很显然,cpu启动完成后会执行该进程,在需要时通过进程调度执行其他进程。
第358行,callin_flag变量置0,当这个函数中boot cpu启动client cpu时,会等待,直到client cpu把callin_flag变量置1。我们后面会看到。
第359行,得到前面创建的新进程的struct thread_info结构放到cpu_new_thread变量中。
第361到374行,这里有几个分支,我们的系统执行的是第369行的分支prom_startcpu_cpuid,我们看这个函数:
312 void prom_startcpu_cpuid(int cpuid, unsigned long pc, unsigned long arg)
313 {
314 p1275_cmd("SUNW,start-cpu-by-cpuid", P1275_INOUT(3, 0),
315 cpuid, pc, arg);
316 }
p1275_cmd函数在分析head_64.S文件时见过,它实际上就是OBP调用接口,这里调用的服务是根据cpuid启动cpu,其中它的第4个参数pc是变量sparc64_cpu_startup,参数arg是变量cpu_new_thread。很显然,这个函数执行完后,在client cpu上就会以cpu_new_thread为参数执行sparc64_cpu_startup段汇编,从而client cpu开始初始化工作。Client cpu初始化做了哪些工作我们后面再讲,我们先看boot的__cpu_up函数讲完。
第376到380行,for循环里等待,如果client cpu把callin_flag变量置1或者循环时间到期,则退出循环。
第382行,如果callin_flag变量被client cpu置1,则代表client cpu启动成功,则本函数返回0
第385行,否则,打印出错信息。
第388行,cpu_new_thread变量清空。
这样 smp_boot_one_cpu函数就分析完了,它主要是调用 prom_startcpu_cpuid启动client cpu,然后判断client cpu启动是否成功,如果成功则返回0。
回到__cpu_up函数中来。
第1236到1249行的分支语句,如果client cpu启动成功的话就会执行,
第1237行,为client cpu设置smp_commenced_mask位图。
第1237行,在client cpu启动的过程中会设置cpu_online_map位图,如果还没有设置,则这里等待client cpu设置该位图。
第1240行,如果仍然没有设置cpu_online_map,则cient cpu启动失败,返回错误信息。
第1246行,如果client cpu设置了cpu_online_map位图,则调用smp_synchronize_one_tick函数同步其时钟,不过我们平台是不会执行的,因为在hypervisor平台下时钟寄存器是不可写的。
到这里__cpu_up函数就执行完了,client cpu的初始化过程是在sparc64_cpu_startup中完成的,我们来看这段汇编,由于在head_64.S文件中汇编的分析是一行行的分析,读者应该已经熟悉sparc的汇编,因此我们这里分析汇编一段段的分析,不再那么详细:
41 __CPUINIT
42 .align 8
43 .globl sparc64_cpu_startup, sparc64_cpu_startup_end
44 sparc64_cpu_startup:
45 BRANCH_IF_SUN4V(g1, niagara_startup)
46 BRANCH_IF_CHEETAH_BASE(g1, g5, cheetah_startup)
47 BRANCH_IF_CHEETAH_PLUS_OR_FOLLOWON(g1, g5, cheetah_plus_startup)
第45行,宏 BRANCH_IF_SUN4V在head_64.S文件的分析中已经分析过,它会跳转到 niagara_startup处执行,我们直接来看 niagara_startup。
85 niagara_startup:
86 /* Disable STICK_INT interrupts. */
87 sethi %hi(0x80000000), %g5
88 sllx %g5, 32, %g5
89 wr %g5, %asr25
90
91 ba,pt %xcc, startup_continue
92 nop
第86到89行,注释里已经说了,这里是禁止时钟中断,方法是把%asr25寄存器的第64位值1。
第91行,跳转到startup_continue处执行。
99 startup_continue:
100 mov %o0, %l0
101 BRANCH_IF_SUN4V(g1, niagara_lock_tlb)
102
第100行,把参数%o0保存到%l0寄存器中,这里的参数实际上就是前面见过的cpu_new_thread,也就是idle进程内核栈。
第101行,跳转到 niagara_lock_tlb处执行
220 niagara_lock_tlb:
221 sethi %hi(KERNBASE), %l3
222 sethi %hi(kern_locked_tte_data), %l4
223 ldx [%l4 + %lo(kern_locked_tte_data)], %l4
224 clr %l5
225 sethi %hi(num_kernel_image_mappings), %l6
226 lduw [%l6 + %lo(num_kernel_image_mappings)], %l6
227 add %l6, 1, %l6
228
229 1:
230 mov HV_FAST_MMU_MAP_PERM_ADDR, %o5
231 sllx %l5, 22, %g2
232 add %l3, %g2, %o0
233 clr %o1
234 add %l4, %g2, %o2
235 mov HV_MMU_IMMU, %o3
236 ta HV_FAST_TRAP
237
238 mov HV_FAST_MMU_MAP_PERM_ADDR, %o5
239 sllx %l5, 22, %g2
240 add %l3, %g2, %o0
241 clr %o1
242 add %l4, %g2, %o2
243 mov HV_MMU_DMMU, %o3
244 ta HV_FAST_TRAP
245
246 add %l5, 1, %l5
247 cmp %l5, %l6
248 bne,pt %xcc, 1b
249 nop
niagara_lock_tlb段做什么呢,实际上client cpu初始化过程中的所有工作,在boot cpu初始化的过程中都做过同样的事情,这里只不过是在client cpu重新做一次,所以这里client cpu所做的工作我们之前都分析过, niagara_lock_tlb段也是一样。在setup_arch函数的分析中,我们在setup_arch()->paging_init()中分析过内核页表的建立过程,在其中的inherit_prom_mappings函数中,会把内核映像的地址转换关系锁存到boot cpu tlb中(可以参考setup_arch函数分析那一章),在client cpu上同样会在tlb中锁存内核映像的地址映射关系。同时锁存到指令tlb和数据tlb中。这就是 niagara_lock_tlb所做的主要工作。
第221到227行,KERNBASE,kern_locked_tte_data变量和num_kernel_image_mappings变量我们之前都见过,他们分别是内核映像的虚拟基地址,内核页tlb表项的data字段和内核映像的tlb 表项数目(也就是地址映射关系的数目),这里把他们分别放到l3,l4和l6寄存器中。
第230到236行,调用sun4v_mmu_map_perm_addr所存一个内核tlb表项到指令tlb中。
第238到246行,调用sun4v_mmu_map_perm_addr所存一个内核tlb表项到数据tlb中。
第256到249行,与前面的第230行到第246行形成一个循环以所存所有的内核映像的地址映射关系。
niagara_lock_tlb就分析完了,接着下面的汇编分析
251 after_lock_tlb:
252 wrpr %g0, (PSTATE_PRIV | PSTATE_PEF), %pstate
253 wr %g0, 0, %fprs
254
255 wr %g0, ASI_P, %asi
256
257 mov PRIMARY_CONTEXT, %g7
258
259 661: stxa %g0, [%g7] ASI_DMMU
260 .section .sun4v_1insn_patch, "ax"
261 .word 661b
262 stxa %g0, [%g7] ASI_MMU
263 .previous
264
265 membar #Sync
266 mov SECONDARY_CONTEXT, %g7
267
268 661: stxa %g0, [%g7] ASI_DMMU
269 .section .sun4v_1insn_patch, "ax"
270 .word 661b
271 stxa %g0, [%g7] ASI_MMU
272 .previous
273
274 membar #Sync
上面这一打断的主要工作是把MMU上的PRIMARY_CONTEXT寄存器和 SECONDARY_CONTEXT寄存器置0,在内核空间这两个寄存器的值为0,在setup_arch函数中分析过。
285 sethi %hi(tramp_stack), %g1
286 or %g1, %lo(tramp_stack), %g1
287 add %g1, TRAMP_STACK_SIZE, %g1
288 sub %g1, STACKFRAME_SZ + STACK_BIAS + 256, %sp
289 mov 0, %fp
这一段实际上是设置接下来初始化所使用的栈为 tramp_stack,这里也许你会奇怪为什么我们这里不使用cpu_new_thread作为接下来初始化时的栈呢,这时因为cpu_new_thread的栈空间并不处于内核映像中,而前面只是把内核映像的地址映射关系锁存到了tlb中,因此如果使用cpu_new_thread作为栈,当访问栈时就会产生tlb miss trap,在tlb miss的处理中为cpu_new_thread栈建立地址映射关系,但是现在client cpu还没有初始化trap table,因此是无法产生trap的,不然会产生严重问题,所以这里不能使用cpu_new_thread作为接下来初始化时的栈。 tramp_stack这段栈空间确是位于内核映像中的,现在访问它没有任何问题。
291 /* Put garbage in these registers to trap any access to them. */
292 set 0xdeadbeef, %g4
293 set 0xdeadbeef, %g5
294 set 0xdeadbeef, %g6
把 0xdeadbeef放入g4,g5,g6寄存器中,实际上值 0xdeadbeef没有任何意义,这里只是让该值占据这几个寄存器,以使这几个寄存器的值非0。
296 call init_irqwork_curcpu
297 nop
调用 init_irqwork_curcpu函数,这个函数我们之前setup_arch的分析中也分析过,它实际上是初始化client cpu的trap_block变量的irq_worklist_pa成员
299 sethi %hi(tlb_type), %g3
300 lduw [%g3 + %lo(tlb_type)], %g2
301 cmp %g2, 3
302 bne,pt %icc, 1f
303 nop
这一段判断 tlb_type,如果tlb_type!=hypervisor,则跳转,显然我们是不会跳转的。
305 call hard_smp_processor_id
306 nop
&nbs
p; 得到client cpu的cpuid
308 call sun4v_register_mondo_queues
309 nop
调用 sun4v_register_mondo_queues函数,这个函数会设置client cpu的4个中断mondo队列。在boot cpu上,该函数是在init_IRQ函数中初始化中断时执行的,可以参考Linux T2中断机制实现的章节。
311 1: call init_cur_cpu_trap
312 ldx [%l0], %o0
以%l0为参数init_cur_cpu_trap,%l0实际上就是idle进程的内核栈cpu_new_thread,该函数会把该栈放入client cpu 的trap_block变量的thread中。
314 /* Start using proper page size encodings in ctx register. */
315 sethi %hi(sparc64_kern_pri_context), %g3
316 ldx [%g3 + %lo(sparc64_kern_pri_context)], %g2
317 mov PRIMARY_CONTEXT, %g1
318
319 661: stxa %g2, [%g1] ASI_DMMU
320 .section .sun4v_1insn_patch, "ax"
321 .word 661b
322 stxa %g2, [%g1] ASI_MMU
323 .previous
324
325 membar #Sync
这一段实际上是把client cpu的MMU上的 PRIMARY_CONTEXT寄存器置为 sparc64_kern_pri_context,实际上 sparc64_kern_pri_context就是0.
326
327 wrpr %g0, 0, %wstate
清0 wstate寄存器。
329 sethi %hi(prom_entry_lock), %g2
330 1: ldstub [%g2 + %lo(prom_entry_lock)], %g1
331 brnz,pn %g1, 1b
332 nop
这一段是获得 prom_entry_lock自旋锁,以对后面的代码实施保护
338 sethi %hi(init_thread_union), %g6
339 or %g6, %lo(init_thread_union), %g6
把 init_thread_union放到g6寄存器中。
341 sethi %hi(is_sun4v), %o0
342 lduw [%o0 + %lo(is_sun4v)], %o0
343 brz,pt %o0, 2f
344 nop
判断 is_sun4v,如果为0,跳转到2处,我们显然不会跳转。
346 TRAP_LOAD_TRAP_BLOCK(%g2, %g3)
347 add %g2, TRAP_PER_CPU_FAULT_INFO, %g2
348 stxa %g2, [%g0] ASI_SCRATCHPAD
宏 TRAP_LOAD_TRAP_BLOCK获得client cpu的trap_block变量。这一段的作用是把trap_block变量的 fault_info放到client cpu的 SCRATCHPAD寄存器0中。
354 sethi %hi(KERNBASE), %g3
355 sub %g2, %g3, %g2
356 sethi %hi(kern_base), %g3
357 ldx [%g3 + %lo(kern_base)], %g3
358 add %g2, %g3, %o1
359 sethi %hi(sparc64_ttable_tl0), %o0
360
361 set prom_set_trap_table_name, %g2
362 stx %g2, [%sp + 2047 + 128 + 0x00]
363 mov 2, %g2
364 stx %g2, [%sp + 2047 + 128 + 0x08]
365 mov 0, %g2
366 stx %g2, [%sp + 2047 + 128 + 0x10]
367 stx %o0, [%sp + 2047 + 128 + 0x18]
368 stx %o1, [%sp + 2047 + 128 + 0x20]
369 sethi %hi(p1275buf), %g2
370 or %g2, %lo(p1275buf), %g2
371 ldx [%g2 + 0x08], %o1
372 call %o1
373 add %sp, (2047 + 128), %o0
上面这一段做的工作设置client cpu的trap table,见setup_arch函数的分析中的setup_trap_table汇编的分析,这里跟那里差不多。
375 ba,pt %xcc, 3f
376 nop 跳转到标记3处执行。
392 3: sethi %hi(prom_entry_lock), %g2
393 stb %g0, [%g2 + %lo(prom_entry_lock)]
这两行的作用是释放 prom_entry_lock自旋锁。
395 ldx [%l0], %g6
396 ldx [%g6 + TI_TASK], %g4
l0寄存器保存的是idle进程内核栈的基地址也就是其struct thread_info结构的基地址。
这里使g6寄存器指向它。同时使g4寄存器指向idle进程的进程描述符。从此以后,在该client cpu上,g4总是指向该cpu的当前执行进程的进程描述符,g6总是指向该cpu当前执行进程的struct thread_info结构。
398 mov 1, %g5
399 sllx %g5, THREAD_SHIFT, %g5
400 sub %g5, (STACKFRAME_SZ + STACK_BIAS), %g5
401 add %g6, %g5, %sp
402 mov 0, %fp
第396到401行,%sp则就是该client cpu运行时的栈指针了,这里把它初始化为当前进程的&(thread_info) +THREAD_SHIFT- STACKFRAME_SZ- STACK_BIAS,在初始化号trap talbe 后就可以使用进程的内核栈作为执行时栈了。
第402行,清空fp寄存器,这一行的作用实际上禁止浮点单元。
04 rdpr %pstate, %o1
405 or %o1, PSTATE_IE, %o1
406 wrpr %o1, 0, %pstate
这一段的作用实际上允许中断,这里只是把pstate寄存器的中断允许位打开,要想时中断真正能够使能,还需要置pil寄存器的中断级别小于15,如果pil=15,则除了少数非屏蔽中断外,所有中断都被禁止。
408 call smp_callin
409 nop
410 call cpu_idle
411 mov 0, %o0
412 call cpu_panic
413 nop
414 1: b,a,pt %xcc, 1b
415
416 .align 8
417 sparc64_cpu_startup_end:
第408行,调用smp_callin函数,到这里实际上client cpu的初始化就已经完成了,接下来如果cpu空闲则执行idle进程,否则通过进程调度执行其他进程。
第410行,调用cpu_ilde执行idle进程。
第412到417行,这几行在正常情况下永远不会执行,除非系统或cpu出错。
上面client cpu初始化的最后一个函数是 smp_callin,我们来看这个函数:
87 void __cpuinit smp_callin(void)
88 {
89 int cpuid = hard_smp_processor_id();
90
91 __local_per_cpu_offset = __per_cpu_offset(cpuid);
92
93 if (tlb_type == hypervisor)
94 sun4v_ktsb_register();
95
96 __flush_tlb_all();
97
98 setup_sparc64_timer();
99
100 if (cheetah_pcache_forced_on)
101 cheetah_enable_pcache();
102
103 local_irq_enable();
104
105 callin_flag = 1;
106 __asm__ __volatile__("membar #Sync\n\t"
107 "flush %%g6" : : : "memory");
108
109 /* Clear this or we will die instantly when we
110 * schedule back to this idler...
111 */
112 current_thread_info()->new_child = 0;
113
114 /* Attach to the address space of init_task. */
115 atomic_inc(&init_mm.mm_count);
116 current->active_mm = &init_mm;
117
118 /* inform the notifiers about the new cpu */
119 notify_cpu_starting(cpuid);
120
121 while (!cpu_isset(cpuid, smp_commenced_mask))
122 rmb();
123
124 ipi_call_lock_irq();
125 cpu_set(cpuid, cpu_online_map);
126 ipi_call_unlock_irq();
127
128 /* idle thread is expected to have preempt disabled */
129 preempt_disable();
130 }
第91行,通过宏__per_cpu_offset得到client cpu的per_cpu域的基地址。
第93到96行,这几行在setup_arch函数的分析中分析过,这里向hypervisor注册后在系统运行的过程中通过tlb miss trap会填充tsb从而快速进行虚拟地址转换。
第98行,初始化client cpu的clock_event_device对象,详细解释参考Linux T2 时钟实现的章节。
第100,101行,这两行不是我们的平台所关心的。
第103行,允许中断,这里是把pil寄存器的中断级别置0.
第104行,callin_flag应该很熟悉吧,在client cpu初始化的过程中,boot cpu会等待知道callin_flag被置为1,它就是在这里被client cpu置为1的。
第106,107行是进行指令流控制的,就不多说了。
第112行,初始化当前进程的thead_info结构的new_child成员为0。
第115行,增加init_mm的引用计数,init_mm在setup_arch的分析中已经提过了,它的pgd成员保存有内核空间页表的基地址。每个client cpu上都有一个idle进程,它们是从boot cpu的init进程派生来的,这些所有的进程共享一个init_mm结构,因此每当初始化其中一个进程的进程描述符的active_mm成员为init_mm时,都要吧init_mm的引用计数加一。
第116行,把idle进程的进程描述符的active_mm成员为init_mm。
第118行,每当启动一个cpu时,内核中的其他子系统可能关心这一活动,以做相应的处理,这里就是利用通知链机制通知那些子系统做相应处理。
第121,判断smp_commenced_mask位图中代表该client cpu的位是否被设置,在前面分析启动client cpu时boot cpu的行为时,提到过smp_commenced_mask位图,对于每一个client cpu,boot cpu都会为其设置smp_commenced_mask位图。这里就就是等待boot cpu设置smp_commenced_mask。
第124到126行,这3行是设置cpu_online_map位图,启动了该client cpu之后当然要为其设置cpu_online_map位图了。
第129行,禁止抢占。
到这里,smp_callin函数就分析完了, 我们继续回到sparc64_cpu_startup汇编中来,
第410行,cpu_idle函数实际上就是进入idle进程执行了,该函数会判断是否有进程调度,如果没有则会一直循环,在正常情况下,client cpu执行到这里就代表该client cpu的启动过程已经完成了,当需要时通过进程调度就可以进行正常的工作了。
第412到414,这几条语句在正常情况下永远不会执行,一旦他们被执行,就代表当前cpu上发生了致命的错误。这几条语句就不分析了。
这样,T2 Linux的SMP的初始化就分析完了。
T2 Linux cpu的分配:
T2 Linux cpu分配的代码位于arch/sparc/kernel/cpumap.c文件中。为了管理系统中所有的cpu,为系统中所有的cpu资源建立了一个树,我们称之为cpu树。这个树分为4层,每层中的某个cpu节点都代表从某一视角来看系统中的某个cpu资源,因此不同的层拥有不同的级别,这些级别定义如下:
enum {
CPUINFO_LVL_ROOT = 0,
CPUINFO_LVL_NODE,
CPUINFO_LVL_CORE,
CPUINFO_LVL_PROC,
CPUINFO_LVL_MAX,
};
其中级别CPUINFO_LVL_ROOT代表了树中的最高层,该层只有一个节点,也就是cpu树的根节点,该层把系统中的所有cpu看作是一个整体。
CPUINFO_LVL_NODE代表了cpu树的第二层,他是从NMUA结构节点的视野来看的,NMUA结构中,每个cpu节点可以拥有各自的内存和资源,一个T2 cpu(包括64个虚拟cpu)都可以作为NMUA结构的一个几点。该层的每个节点都代表了一个NUMA节点。我们的系统不是NUMA结构的,因此该层和CPUINFO_LVL_ROOT层一样,只有一个节点。
CPUINFO_LVL_CORE层是从T2 cpu的物理核的视野来看的,CPUINFO_LVL_NODE层的每个节点在该层都有8个字节点,其中每个节点代表了一个T2 cpu上的8个物理核心中的一个。
CPUINFO_LVL_PROC是从线程(strand)组的视野来看的,T2 cpu的每个物理核心上有两个线程组,其中每个线程组中有4个线程,因此CPUINFO_LVL_CORE层的每个节点在该层都有2个子节点。
每个cpu节点的描述结构定义如下:
struct cpuinfo_node {
int id;
int level;
int num_cpus; /* Number of CPUs in this hierarchy */
int parent_index;
int child_start; /* Array index of the first child node */
int child_end; /* Array index of the last child node */
int rover; /* Child node iterator */
};
所有的cpu节点在内存中时按照节点的所属层级及该节点在该层所代表视野的id号在内存顺寻排放,构成了一个节点数组。该数组的第一个元素时根节点,接下来是CPUINFO_LVL_NODE层的节点,它们按照NUMA节点号从小到达排放,再接下来是CPUINFO_LVL_CORE层的节点,它们按照core_id号从小到达排放,依次类推。
我们现在来解释struct cpuinfo_node结构中个成员的含义:
Id该节点所代表cpu资源的id号,在不同的层级,id号的含义时不同的,在CPUINFO_LVL_NODE层时NUMA节点号,在 CPUINFO_LVL_NODE是物理核id号…
Level成员代表该节点处于节点树的哪一层。
代表整个cpu树的结构为struct cpuinfo_tree树结构,定义如下:
struct cpuinfo_tree {
int total_nodes;
/* Offsets into nodes[] for each level of the tree */
struct cpuinfo_level level[CPUINFO_LVL_MAX];
struct cpuinfo_node nodes[0];
}
其中total_nodes成员代表该cpu树中总共有多少节点。
Level成员是cpu树中每一层的描述结构,struct cpuinfo_level结构定义如下:
struct cpuinfo_level {
int start_index; /* Index of first node of a level in a cpuinfo tree */
int end_index; /* Index of last node of a level in a cpuinfo tree */
int num_nodes; /* Number of nodes in a level in a cpuinfo tree */
};
struct cpuinfo_level结构的各成员就不解释了,它后面的注释说的很清楚。
struct cpuinfo_tree结构的nodes成员则代表cpu树的节点数组了。
建立cpu树的函数为cpu_map_rebuild函数,该函数定义如下:
424 void cpu_map_rebuild(void)
425 {
426 unsigned long flag;
427
428 spin_lock_irqsave(&cpu_map_lock, flag);
429 _cpu_map_rebuild();
430 spin_unlock_irqrestore(&cpu_map_lock, flag);
431 }
该函数实际上是调用函数_cpu_map_rebuild函数,定义如下:
343 static void _cpu_map_rebuild(void)
344 {
345 int i;
346
347 if (cpuinfo_tree) {
348 kfree(cpuinfo_tree);
349 cpuinfo_tree = NULL;
350 }
351
352 cpuinfo_tree = build_cpuinfo_tree();
353 if (!cpuinfo_tree)
354 return;
355
356 /* Build CPU distribution map that spans all online CPUs. No need
357 * to check if the CPU is online, as that is done when the cpuinfo
358 * tree is being built.
359 */
360 for (i = 0; i < cpuinfo_tree->nodes[0].num_cpus; i++)
361 cpu_distribution_map[i] = iterate_cpu(cpuinfo_tree, 0);
362 }
第347到350行,如果系统中cpu树已存在,则释放它。cpu树反映的当前系统中处于活动状态(online)的cpu,因此当cpu节点树建立好后,它并不是一成不变的,随时处于动态更新中,必要时会调用cpu_map_rebuild函数重新建立cpu树,这时就要删去以前的cpu树了。
第352行,调用build_cpuinfo_tree函数建立cpu树,我们来看这个函数:
184 static struct cpuinfo_tree *build_cpuinfo_tree(void)
185 {
186 struct cpuinfo_tree *new_tree;
187 struct cpuinfo_node *node;
188 struct cpuinfo_level tmp_level[CPUINFO_LVL_MAX];
189 int num_cpus[CPUINFO_LVL_MAX];
190 int level_rover[CPUINFO_LVL_MAX];
191 int prev_id[CPUINFO_LVL_MAX];
192 int n, id, cpu, prev_cpu, last_cpu, level;
193
194 n = enumerate_cpuinfo_nodes(tmp_level);
195
196 new_tree = kzalloc(sizeof(struct cpuinfo_tree) +
197 (sizeof(struct cpuinfo_node) * n), GFP_ATOMIC);
198 if (!new_tree)
199 return NULL;
200
201 new_tree->total_nodes = n;
202 memcpy(&new_tree->level, tmp_level, sizeof(tmp_level));
203
204 prev_cpu = cpu = first_cpu(cpu_online_map);
205
206 /* Initialize all levels in the tree with the first CPU */
207 for (level = CPUINFO_LVL_PROC; level >= CPUINFO_LVL_ROOT; level--) {
208 n = new_tree->level[level].start_index;
209
210 level_rover[level] = n;
211 node = &new_tree->nodes[n];
212
213 id = cpuinfo_id(cpu, level);
214 if (unlikely(id < 0)) {
215 kfree(new_tree);
216 return NULL;
217 }
218 node->id = id;
219 node->level = level;
220 node->num_cpus = 1;
221
222 node->parent_index = (level > CPUINFO_LVL_ROOT)
223 ? new_tree->level[level - 1].start_index : -1;
224
225 node->child_start = node->child_end = node->rover =
226 (level == CPUINFO_LVL_PROC)
227 ? cpu : new_tree->level[level + 1].start_index;
228
229 prev_id[level] = node->id;
230 num_cpus[level] = 1;
231 }
232
233 for (last_cpu = (num_possible_cpus() - 1); last_cpu >= 0; last_cpu--) {
234 if (cpu_online(last_cpu))
235 break;
236 }
237
238 while (++cpu <= last_cpu) {
239 if (!cpu_online(cpu))
240 continue;
241
242 for (level = CPUINFO_LVL_PROC; level >= CPUINFO_LVL_ROOT;
243 level--) {
244 id = cpuinfo_id(cpu, level);
245 if (unlikely(id < 0)) {
246 kfree(new_tree);
247 return NULL;
248 }
249
250 if ((id != prev_id[level]) || (cpu == last_cpu)) {
251 prev_id[level] = id;
252 node = &new_tree->nodes[level_rover[level]];
253 node->num_cpus = num_cpus[level];
254 num_cpus[level] = 1;
255
256 if (cpu == last_cpu)
257 node->num_cpus++;
258
259 /* Connect tree node to parent */
260 if (level == CPUINFO_LVL_ROOT)
261 node->parent_index = -1;
262 else
263 node->parent_index =
264 level_rover[level - 1];
265
266 if (level == CPUINFO_LVL_PROC) {
267 node->child_end =
268 (cpu == last_cpu) ? cpu : prev_cpu;
269 } else {
270 node->child_end =
271 level_rover[level + 1] - 1;
272 }
273
274 /* Initialize the next node in the same level */
275 n = ++level_rover[level];
276 if (n <= new_tree->level[level].end_index) {
277 node = &new_tree->nodes[n];
278 node->id = id;
279 node->level = level;
280
281 /* Connect node to child */
282 node->child_start = node->child_end =
283 node->rover =
284 (level == CPUINFO_LVL_PROC)
285 ? cpu : level_rover[level + 1];
286 }
287 } else
288 num_cpus[level]++;
289 }
290 prev_cpu = cpu;
291 }
292
293 return new_tree;
294 }
第194行,enumerate_cpuinfo_nodes函数会初始化tmp_level,并计算出所有的节点数目,我们来看这个该函数:
123 static int enumerate_cpuinfo_nodes(struct cpuinfo_level *tree_level)
124 {
125 int prev_id[CPUINFO_LVL_MAX];
126 int i, n, num_nodes;
127
128 for (i = CPUINFO_LVL_ROOT; i < CPUINFO_LVL_MAX; i++) {
129 struct cpuinfo_level *lv = &tree_level[i];
130
131 prev_id[i] = -1;
132 lv->start_index = lv->end_index = lv->num_nodes = 0;
133 }
134
135 num_nodes = 1; /* Include the root node */
136
137 for (i = 0; i < num_possible_cpus(); i++) {
138 if (!cpu_online(i))
139 continue;
140
141 n = cpuinfo_id(i, CPUINFO_LVL_NODE);
142 if (n > prev_id[CPUINFO_LVL_NODE]) {
143 tree_level[CPUINFO_LVL_NODE].num_nodes++;
144 prev_id[CPUINFO_LVL_NODE] = n;
145 num_nodes++;
146 }
147 n = cpuinfo_id(i, CPUINFO_LVL_CORE);
148 if (n > prev_id[CPUINFO_LVL_CORE]) {
149 tree_level[CPUINFO_LVL_CORE].num_nodes++;
150 prev_id[CPUINFO_LVL_CORE] = n;
151 num_nodes++;
152 }
153 n = cpuinfo_id(i, CPUINFO_LVL_PROC);
154 if (n > prev_id[CPUINFO_LVL_PROC]) {
155 tree_level[CPUINFO_LVL_PROC].num_nodes++;
156 prev_id[CPUINFO_LVL_PROC] = n;
157 num_nodes++;
158 }
159 }
160
161 tree_level[CPUINFO_LVL_ROOT].num_nodes = 1;
162
163 n = tree_level[CPUINFO_LVL_NODE].num_nodes;
164 tree_level[CPUINFO_LVL_NODE].start_index = 1;
165 tree_level[CPUINFO_LVL_NODE].end_index = n;
166
167 n++;
168 tree_level[CPUINFO_LVL_CORE].start_index = n;
169 n += tree_level[CPUINFO_LVL_CORE].num_nodes;
170 tree_level[CPUINFO_LVL_CORE].end_index = n - 1;
171
172 tree_level[CPUINFO_LVL_PROC].start_index = n;
173 n += tree_level[CPUINFO_LVL_PROC].num_nodes;
174 tree_level[CPUINFO_LVL_PROC].end_index = n - 1;
175
176 return num_nodes;
177 }
第128到133行,这个for循环吧tmp_level中所有的元素(代表每一层的描述结构)的所有成员都初始化为0,并把prev_id数组的所有成员都初始化为-1。
第135,num_nodes初始化为1,之所以不初始化为0是因为根节点时肯定存在的。
第137到159行的for循环,对系统中的每一个"online"状态的cpu,对于CPUINFO_LVL_NODE,CPUINFO_LVL_CORE,CPUINFO_LVL_PROC三个层级,分别调用cpuinfo_id函数得到该cpu所属的NUMA节点,所属的物理核心,所属的线程组,每当新发现一个NUMA节点,物理核心和线程组,都会对tmp_level中代表该节点的层级描述结构中的num_nodes成员自加一,这样,当for循环执行完后,tmp_level的CPUINFO_LVL_NODE,CPUINFO_LVL_CORE,CPUINFO_LVL_PROC三个成员中就保存了这三个层级中的节点的数目。
第161行,把tmp_leval中CPUINFO_LVL_ROOT层的描述结构的节点数目置为1。
第163到165行,初始化tmp_level中CPUINFO_LVL_NODE层的描述结构的start_index成员和end_index成员。他们其实就是该层节点在节点数组中第一个节点的位置和最后一个节点的位置。
第167到170行,初始化tmp_level中CPUINFO_LVL_CORE层的描述结构的start_index成员和end_index成员。
第172到175行,初始化tmp_level中CPUINFO_LVL_PROC层的描述结构的start_index成员和end_index成员。
第176行,返回cpu树中所有节点的数目。
我们现在在来分析一下enumerate_cpuinfo_nodes函数中用到的cpuinfo_id,它根据online cpu的cpuid找出该cpu所属的NUMA节点,物理核心和线程组:
95 static int cpuinfo_id(int cpu, int level)
96 {
97 int id;
98
99 switch (level) {
100 case CPUINFO_LVL_ROOT:
101 id = 0;
102 break;
103 case CPUINFO_LVL_NODE:
104 id = cpu_to_node(cpu);
105 break;
106 case CPUINFO_LVL_CORE:
107 id = cpu_data(cpu).core_id;
108 break;
109 case CPUINFO_LVL_PROC:
110 id = cpu_data(cpu).proc_id;
111 break;
112 default:
113 id = -EINVAL;
114 }
115 return id
116 }
第100,101行,整个cpu树只有一个根节点,因此直接赋为0。
第103,104行,通过cpu_to_node宏得到该online cpu所属的物理核心的core_id,还记得每个cpu的per_cpu变量cpuinfo_sparc结构吗,它的core_id成员保存了该cpu所属的物理核心的core_id,这里就是从那得到core_id。
第109,110行,从个cpu的per_cpu变量cpuinfo_sparc结构的proc_id成员中得到该cpu所属的线程组的组id。
现在回到build_cpuinfo_tree函数中来。
之前的enumerate_cpuinfo_nodes函数在tmp_level数组中保存了cpu树中各层的节点数目以及每层的起始节点和终止节点在节点数组中的位置。
第196行,为cpu树 分配空间,包括cpu树的描述结构struct cpuinfo_tree以及cpu树中所有的节点的空间。
第202行,把tmp_level变量复制到cpu树描述结构的level成员中。
第204行,把第一个online cpu的cpuid保存在变量prev_cpu中。
第207行,这里是一个for循环,它对cpu树中的每一层都执行一次循环体。
第208行,得到当前层在节点数组中的第一个节点的位置放到变量n中。
第210行,把n放在level_rover数组中代表当前层的元素中,这样level_rover数组中就存放有每一层中的第一个节点在节点数组中的位置,这里只是先把它初始化为第一个节点,实际上对于在当前层中新发现的一个节点来树level_recover数组中保存的是当前层中已经存在的最后一个节点,即相对于新发现节点的前一个节点,我们后面会看到。
第211行,node指向节点数组中当前层的第一个节点。
第213行,调用cpuinfo_id函数(在上文中分析过)利用第一个online cpu的cpuid得到当前层的第一个节点的id。
第218到220行,初始化当前层的第一个节点,其id成员初始化为第213行得到id,level成员初始化为当前层,num_cpus成员初始化为1。
第222行,初始化当前层第一个节点的parent_index成员,它实际上就是初始化成上一层的第一个节点在节点数组中的索引。
第225行,初始化当前层第一个节点的child_start,child_end成员与rover成员为其下一层的第一个节点在节点数组中的索引。
第229行,初始化prev_id数组中的该层成员为当前层的第一个节点的id。
第230行,初始化num_cpus数组中的该层成员为1。
这样第207到231行的for循环就分析完了,它实际上时初始化了各层的第一个节点。
第233到236行的for循环,在possible cpu位图中,从最后一个possible cpu往前找,找到第一个online cpu,放到last_cpu变量中。
第238到291行的while循环遍历系统中除第一个online cpu外的所有online cpu(第一个online cpu在之前已经初始化过)。然后根据这些online cpu初始化cpu树中的所有节点。
第242行是一个for循环,它对第238到291行的while循环遍历的每一个online cpu,都遍历cpu树中的每一层,我们看这个循环体:
第244行,调用cpuinfo_id函数得到该online cpu在当前层的所属的节点的id。
第250行,如果prev_id数组中代表当前层的元素中保存的id与第244行得到的节点id相同,则说明第244行得到的当前层的节点在之前已经初始化过,执行第287行else语句中的内容把num_cpus数组中代表当前层的元素的值加1,否则则代表该节点没有初始化过,是当前层中新发现的一个节点,在if语句中会初始化该节点:
第251行,更新prev_id数组中代表当前层的元素。
第252行,node指向当前层中的前一个节点,前一个节点的索引保存在level_rover数组中。
第253行,把前一个节点的num_cpus成员赋值为num_cpus数组中代表当前层的值。节点的num_cpus成员保存有代表该节点所包含的虚拟cpu的数目,比如一个物理核心包含8个虚拟cpu,如果这八个虚拟cpu都是online的话,代表该物理核心的节点的num_cpus成员应该是8。对于当前层来树,如果新发现一个节点,则表示前一个节点所包含的所有online cpu都已经遍历完,而前一个节点的online cpus的数目也已经保存在num_cpus数组中(通过第288行的else语句)。因此每发现一个新的节点,都要为前一个节点的num_cpus成员赋值。
第254行,因为是新发现的节点,所以把num_cpus数组中代表该层的元素初始化为1。
第259行到264行,初始化前一个节点的parent_index成员为level_recover数组中保存的前一层的节点,该节点实际上就是上一层的节点中最新发现的节点。
第266到272行,初始化前一个节点的child_end成员,如果当前层是CPUINFO_LVL_PROC(cpu树中的最后一层,它没有子节点,因此该层节点的child_start和chlid_end成员保存的是该线程组的第一个虚拟cpu的cpuid和最后一个虚拟cpu的cpuid),则chlid_end成员赋值为前一个cpu的cpuid(保存在prev_cpu变量中)。否则如果不是CPUINFO_LVL_PROC,则child_end成员赋值为level_rover代表下一层的元素中保存的最新节点在节点数组中的索引。
第275行,更新level_rover数组中代表当前层的元素,同时把该值存放到变量n中。
第276行,判断n是否合法。
第277行,node指向当前层新发现的节点。
第278行,初始化节点的id成员。
第279汗,初始化节点的level成员。
第282到285行,初始化节点的child_start和rover成员。
第288行,前面已经提过。
第290行,更新prev_cpu变量。
到这里whlie循环就分析玩了,当while循环执行玩时,cpu树中的每个节点都被初始化了,cpu树也就建立起来了。
第293行,返回指向新建立的节点树的指针。
到这里build_cpuinfo_tree函数就分析完了,我们回到_cpu_map_rebuild函数中来。
第353行,判断cpu树是否建立成功。
第360,361行是一个for循环,这个for循环实际上是为cpu_distribution_map数组的元素赋值,这时虽然建立了cpu树,但是分配cpu时直接从cpu树中去分配实际上是非常慢的,这时就需要cpu_distribution_map数组来加速cpu分配了,分配cpu时直接按照cpu_distribution_map数组中的元素顺序分配,比如第一个任务分配给cpu_distribution_map[0]中保存的cpuid的cpu,第二个任务分配给cpu_distribution_map[1]中保存的cpuid的cpu,依次类推。要使这种策略有效奥妙就在于cpu_distribution_map数组中保存的cpuid的顺序,上文我们介绍过为了提过任务的并发行,分配的策略是尽量把认为分配到不同的NUMA节点上,不同的物理核心上,不同的线程组中,按照这种规则,那么cpu_distribution_map数组中第一个元素保存的是第一个NUMA节点的第一个online cpu,第二个元素保存的是第二个NUMA节点的第一个online cpu,当所有的NUMA节点的第一个online cpu在cpu_distribution_map数组中保存完后,接下来应该是第一个NUMA节点的第二个物理核心的第一个online cpu…,cpu_distribution_map数组中的元素是利用cpu树来赋值的,iterate_cpu函数就是按照如上的规则每次返回下一个应该分配的cpu的cpuid,来看一看这个函数:
318 static int iterate_cpu(struct cpuinfo_tree *t, unsigned int root_index)
319 {
320 const int *rover_inc_table;
321 int level, new_index, index = root_index;
322
323 switch (sun4v_chip_type) {
324 case SUN4V_CHIP_NIAGARA1:
325 case SUN4V_CHIP_NIAGARA2:
326 rover_inc_table = niagara_iterate_method;
327 break;
328 default:
329 rover_inc_table = generic_iterate_method;
330 }
331
332 for (level = t->nodes[root_index].level; level < CPUINFO_LVL_MAX;
333 level++) {
334 new_index = t->nodes[index].rover;
335 if (rover_inc_table[level] & ROVER_INC_ON_VISIT)
336 increment_rover(t, index, root_index, rover_inc_table);
337
338 index = new_index;
339 }
340 return index;
341 }
分配cpu时通过各节点的rover成员来分配的,在之前rover成员是干什么的一直没有提过,这里我们就知道它是用来干什么用的了。Cpu树是按照层次组织的,而最后一层的child_start,child_end及rover成员实际上保存的就是虚拟cpu的cpuid。按照我们的规则,当从一个NUMA节点中分配一个cpu后,应该从下一个NUMA节点中分配,同理,从一个物理核心中分配一个cpu后应该从下一个物理核心中分配,应次就需要一个变量指向各层中应该从哪一个节点分配cpu,这个变量就是个节点中的rover成员,它指向当下一次从该节点分配cpu时应该该节点的哪一个子节点分配。在初始化时个节点的该成员值都指向了第一个字节点。
第326行,rover_inc_table初始化为niagara_iterate_method,niagara_iterate_method也是一个数组,该数组的作用其实不大,读者可自行分析。
第332到339行的for循环就是从cpu树中分配一个cpu,它从根节点开始,先取出根节点的rover成员所代表的NUMA节点,它表示应该从该NUMA节点中分配,然后根节点的rover成员加一,表示下一次分配从下一个NUMA节点分配,下依次循环时取出NUMA节点的rover成员所代表字节点,然后reover成员子加1,知道循环到最后一层,取出其rover成员,最后一层的rover成员就是要分配的cpu的cpuid,然后把最后一层的rover成员自加1。increment_rover作用就是把各节点的rover成员加1,之所以实现成一个函数是为了处理当分配到各层的最后一个节点的最后一个cpu是,要把rover成员指向其第一个字节点。处理过程读者可自行分析。
第340行,返回分配的cpu的cpuid。
回到_cpu_map_rebuild函数中来,
第360,361行的for循环执行完后,cpu_distribution_map数组就赋值好了,它按照我们要分配的cpu的顺序保存了个online cpu的cpuid,到这里_cpu_map_rebuild函数就分析完了。
当我们要为某个任务分配cpu时,要调用map_to_cpu,该函数的的参数是任务的要分配的任务的索引,我们来看这个函数:
407 int map_to_cpu(unsigned int index)
408 {
409 int mapped_cpu;
410 unsigned long flag;
411
412 spin_lock_irqsave(&cpu_map_lock, flag);
413 mapped_cpu = _map_to_cpu(index);
414
415 #ifdef CONFIG_HOTPLUG_CPU
416 while (unlikely(!cpu_online(mapped_cpu)))
417 mapped_cpu = _map_to_cpu(index);
418 #endif
419 spin_unlock_irqrestore(&cpu_map_lock, flag);
420 return mapped_cpu;
421 }
422 EXPORT_SYMBOL(map_to_cpu)
该函数实际上时调用_map_to_cpu函数来完成具体的分配工作的:
386 static int _map_to_cpu(unsigned int index)
387 {
388 struct cpuinfo_node *root_node;
389
390 if (unlikely(!cpuinfo_tree)) {
391 _cpu_map_rebuild();
392 if (!cpuinfo_tree)
393 return simple_map_to_cpu(index);
394 }
395
396 root_node = &cpuinfo_tree->nodes[0];
397 #ifdef CONFIG_HOTPLUG_CPU
398 if (unlikely(root_node->num_cpus != num_online_cpus())) {
399 _cpu_map_rebuild();
400 if (!cpuinfo_tree)
401 return simple_map_to_cpu(index);
402 }
403 #endif
404 return cpu_distribution_map[index % root_node->num_cpus];
405 }
该函数就不一行行分析了,它的主要的分配工作是第404行,实际上是从cpu_distribution_map数组中取出第index%online cpu number个元素。前面的几行是处理当系统中online cpu的个数变化是,比如系统运行时某个cpu挂起或者某个挂起的cpu进入online状态,这时就需要调用_cpu_map_rebuild函数重新建立cpu树及cpu_distribution_map数组了。
当然如果cpu树没有建立成功,也会有一个简单的cpu分配方式,他是通过simple_map_to_cpu函数来分配的,这里就不分析了。
到这里,cpu的分配就分析完了。
T2 Linux处理器间中断:
处理器间中断的硬件相关的内容在Lniux T2中断机制中已经介绍过了。我们这里主要介绍privileged模式下即Linux中的软件处理部分。
在T2 Linux中处理器间中断分为发送和接收两部分,在Linux中,只有一些特定的场合才需要向其他的cpu发送中断,这些具体场合本文不会提到,也关心,有兴趣的读者可以自行分析,在T2 Linux中,向其他cpu发送中断最终是调用hypervisor的服务来实现的。
T2 Linux中向其它cpu发送中断的函数为xcall_deliver函数,它定义于arch/sparc/kernel/smp_64.c文件中:
740 static void xcall_deliver(u64 data0, u64 data1, u64 data2, const cpumask_t *mask)
741 {
742 struct trap_per_cpu *tb;
743 int this_cpu, i, cnt;
744 unsigned long flags;
745 u16 *cpu_list;
746 u64 *mondo;
747
748 /* We have to do this whole thing with interrupts fully disabled.
749 * Otherwise if we send an xcall from interrupt context it will
750 * corrupt both our mondo block and cpu list state.
751 *
752 * One consequence of this is that we cannot use timeout mechanisms
753 * that depend upon interrupts being delivered locally. So, for
754 * example, we cannot sample jiffies and expect it to advance.
755 *
756 * Fortunately, udelay() uses %stick/%tick so we can use that.
757 */
758 local_irq_save(flags);
759
760 this_cpu = smp_processor_id();
761 tb = &trap_block[this_cpu];
762
763 mondo = __va(tb->cpu_mondo_block_pa);
764 mondo[0] = data0;
765 mondo[1] = data1;
766 mondo[2] = data2;
767 wmb();
768
769 cpu_list = __va(tb->cpu_list_pa);
770
771 /* Setup the initial cpu list. */
772 cnt = 0;
773 for_each_cpu(i, mask) {
774 if (i == this_cpu || !cpu_online(i))
775 continue;
776 cpu_list[cnt++] = i;
777 }
778
779 if (cnt)
780 xcall_deliver_impl(tb, cnt);
781
782 local_irq_restore(flags);
783 }
该函数有4个参数,第一个参数是一个64位数据,其中第32位是要被中断的cpu执行的函数,高32位是MMU上的contexid,大部分情况下为0,第二个和第32个参数是两个64位数据,他们是要被中断的cpu所要执行函数的参数,第4个参数是一个cpu位图,它标志了要向哪些cpu发送中断。
第760行,得到当前cpu的cpuid。
第761行,得到当前cpu的trap_block变量。
第763行,得到trap_block变量的cpu_mondo_block_pa成员,该成员在中断初始化时已经赋值(见Linux T2中断机制那一章)。
第764到766行,把该函数的前三个参数放到trap_block变量的cpu_mondo_block_pa成员中,当调用hypervisor服务时发送中断时,hypervisor会把这3个参数放到被中断的cpu的cpu mondo队列中。
第767行,内存屏障,保证后面的指令执行之前,前面的访存操作已经执行,这里实际上是保证3个参数确切的已经写入到内存中。
第769行,得到trap_block变量的cpu_list_pa成员,它用来保存第4个参数,hypervisor服务会向该变量中的cpu发送中断。
第773到777行的for循环,取出mask参数中的所有cpu,保存到trap_block变量的cpu_list_pa成员中。
第780行,调用xcall_deliver_impl函数来进行接下来的操作,在我们的平台上xcall_deliver_impl被设置为hypervisor_xcall_deliver函数,我们来看这个函数:
626 static void hypervisor_xcall_deliver(struct trap_per_cpu *tb, int cnt)
627 {
628 int retries, this_cpu, prev_sent, i, saw_cpu_error;
629 unsigned long status;
630 u16 *cpu_list;
631
632 this_cpu = smp_processor_id();
633
634 cpu_list = __va(tb->cpu_list_pa);
635
636 saw_cpu_error = 0;
637 retries = 0;
638 prev_sent = 0;
639 do {
640 int forward_progress, n_sent;
641
642 status = sun4v_cpu_mondo_send(cnt,
643 tb->cpu_list_pa,
644 tb->cpu_mondo_block_pa);
645
646 /* HV_EOK means all cpus received the xcall, we're done. */
647 if (likely(status == HV_EOK))
648 break;
649
650 /* First, see if we made any forward progress.
651 *
652 * The hypervisor indicates successful sends by setting
653 * cpu list entries to the value 0xffff.
654 */
655 n_sent = 0;
656 for (i = 0; i < cnt; i++) {
657 if (likely(cpu_list[i] == 0xffff))
658 n_sent++;
659 }
660
661 forward_progress = 0;
662 if (n_sent > prev_sent)
663 forward_progress = 1;
664
665 prev_sent = n_sent;
666
667 /* If we get a HV_ECPUERROR, then one or more of the cpus
668 * in the list are in error state. Use the cpu_state()
669 * hypervisor call to find out which cpus are in error state.
670 */
671 if (unlikely(status == HV_ECPUERROR)) {
672 for (i = 0; i < cnt; i++) {
673 long err;
674 u16 cpu;
675
676 cpu = cpu_list[i];
677 if (cpu == 0xffff)
678 continue;
679
680 err = sun4v_cpu_state(cpu);
681 if (err == HV_CPU_STATE_ERROR) {
682 saw_cpu_error = (cpu + 1);
683 cpu_list[i] = 0xffff;
684 }
685 }
686 } else if (unlikely(status != HV_EWOULDBLOCK))
687 goto fatal_mondo_error;
688
689 /* Don't bother rewriting the CPU list, just leave the
690 * 0xffff and non-0xffff entries in there and the
691 * hypervisor will do the right thing.
692 *
693 * Only advance timeout state if we didn't make any
694 * forward progress.
695 */
696 if (unlikely(!forward_progress)) {
697 if (unlikely(++retries > 10000))
698 goto fatal_mondo_timeout;
699
700 /* Delay a little bit to let other cpus catch up
701 * on their cpu mondo queue work.
702 */
703 udelay(2 * cnt);
704 }
705 } while (1);
706
707 if (unlikely(saw_cpu_error))
708 goto fatal_mondo_cpu_error;
709
710 return;
711
712 fatal_mondo_cpu_error:
713 printk(KERN_CRIT "CPU[%d]: SUN4V mondo cpu error, some target cpus "
714 "(including %d) were in error state\n",
715 this_cpu, saw_cpu_error - 1);
716 return;
717
718 fatal_mondo_timeout:
719 printk(KERN_CRIT "CPU[%d]: SUN4V mondo timeout, no forward "
720 " progress after %d retries.\n",
721 this_cpu, retries);
722 goto dump_cpu_list_and_out;
723
724 fatal_mondo_error:
725 printk(KERN_CRIT "CPU[%d]: Unexpected SUN4V mondo error %lu\n",
726 this_cpu, status);
727 printk(KERN_CRIT "CPU[%d]: Args were cnt(%d) cpulist_pa(%lx) "
728 "mondo_block_pa(%lx)\n",
729 this_cpu, cnt, tb->cpu_list_pa, tb->cpu_mondo_block_pa);
730
731 dump_cpu_list_and_out:
732 printk(KERN_CRIT "CPU[%d]: CPU list [ ", this_cpu);
733 for (i = 0; i < cnt; i++)
734 printk("%u ", cpu_list[i]);
735 printk("]\n");
736 }
第632行,得到当前cpu的cpuid放到this_cpu变量中。
第634行,得到当前cpu的trap_block变量的cpu_list_pa成员放大cpu_list变量中。
第636到638行,初始化几个变量为0。
第675到705行的while循环就不一行行的仔细分析了,它实际上是调用sun4v_cpu_mondo_send函数向其他cpu发送中断,sun4v_cpu_mondo_send函数实际上就是请求发送cpu中断的hypervisor服务,如果成功,即所有的目标cpu都接受到了中断,则该函数返回HV_EOK,这时就推出while循环,否则如果没有成功,即可能只有一部分目标cpu接收到了中断,后者都没有接收到中断,则重试,直到所有的目标cpu都接受到中断或者超时,如果超时,就代表操作失败,会打印出出错信息。
到这里向其他cpu发送处理器间中断的函数就分析完了,哪些需要发送处理器间中断的场合最终都会调用该函数来发送处理器间中断。
T2 Linux接受cpu中断则是通过cpu 中断mondo队列来接受的,发送中断的hypervisor服务会在所有的目标cpu的中断mondo队列中添加一个64字节中断数据包,然后目标cpu就会产生cpu interrupt mondo trap,T2 Linux就会执行该trap的trap处理函数,它实际上就是定义于arch/sparc/kernel/sun4v_ivec.S文件中的sun4v_cpu_mondo函数,我们来看这个函数:
13 sun4v_cpu_mondo:
14 /* Head offset in %g2, tail offset in %g4.
15 * If they are the same, no work.
16 */
17 mov INTRQ_CPU_MONDO_HEAD, %g2
18 ldxa [%g2] ASI_QUEUE, %g2
19 mov INTRQ_CPU_MONDO_TAIL, %g4
20 ldxa [%g4] ASI_QUEUE, %g4
21 cmp %g2, %g4
22 be,pn %xcc, sun4v_cpu_mondo_queue_empty
23 nop
24
25 /* Get &trap_block[smp_processor_id()] into %g4. */
26 ldxa [%g0] ASI_SCRATCHPAD, %g4
27 sub %g4, TRAP_PER_CPU_FAULT_INFO, %g4
28
29 /* Get CPU mondo queue base phys address into %g7. */
30 ldx [%g4 + TRAP_PER_CPU_CPU_MONDO_PA], %g7
31
32 /* Now get the cross-call arguments and handler PC, same
33 * layout as sun4u:
34 *
35 * 1st 64-bit word: low half is 32-bit PC, put into %g3 and jmpl to it
36 * high half is context arg to MMU flushes, into %g5
37 * 2nd 64-bit word: 64-bit arg, load into %g1
38 * 3rd 64-bit word: 64-bit arg, load into %g7
39 */
40 ldxa [%g7 + %g2] ASI_PHYS_USE_EC, %g3
41 add %g2, 0x8, %g2
42 srlx %g3, 32, %g5
43 ldxa [%g7 + %g2] ASI_PHYS_USE_EC, %g1
44 add %g2, 0x8, %g2
45 srl %g3, 0, %g3
46 ldxa [%g7 + %g2] ASI_PHYS_USE_EC, %g7
47 add %g2, 0x40 - 0x8 - 0x8, %g2
48
49 /* Update queue head pointer. */
50 lduw [%g4 + TRAP_PER_CPU_CPU_MONDO_QMASK], %g4
51 and %g2, %g4, %g2
52
53 mov INTRQ_CPU_MONDO_HEAD, %g4
54 stxa %g2, [%g4] ASI_QUEUE
55 membar #Sync
56
57 jmpl %g3, %g0
58 nop
第17,18行,把cpu mondo队列的head寄存器的值读入g2中。
第19,20行,把cpu mondo队列的tail寄存器的值读入g4中。
第21到23行,比较head寄存器和tail寄存器的值,如果相等,则说明cpu mondo队列为空,没有中断需要处理,跳到sun4v_cpu_mondo_queue_empty处执行。
第26行,读取cpu的地址为0的SCRATCHPAD寄存器的值到g4中,该寄存器中存放的是cpu的trap_block变量的fault_info成员的地址。
第27行,计算出cpu的trap_block变量的地址放入g4中。
第30行,读取cpu的trap_block变量的cpu_mondo_block_pa成员的值到g7中,该成员中保存的是cpu的cpu mondo队列的基地址。
第40行,读取head寄存器所指的cpu mondo队列中第一个64字节的中断包的第一个64位数据到g3中,从上文的分析可知,该64位数据的低32位是该接收中断的cpu要执行的函数的地址,高32位是一个contexid。
第41行,g2加8个字节,指向第一个中断包的第二个64为数据,该数据是要执行函数的参数。
第42行,g3的值右移32位放到g5中,这样g5就保存的是contexid.
第43行,读取第二个64位数据到g1中。
第44到46行,读取第三个64位数据放到g7行,该值同样是要执行函数的参数。
第47行,更新g2的值,使之指向cpu mondo队列中的下一个中断包。
第50到55行,更新head寄存器的值,在Linux T2中断机制那一章中讲解设备中断的接收时讲过同样的几条语句,这里跟那里是一样的,可以参考哪里。
第57行,跳到g3中保存的函数中执行,该函数就是通过处理器间中断要求目标cpu执行的函数,就是在这里执行。
到这里T2 Linux的SMP支持就讲完了。