sparc架构代码分析-smp代码分析

本文深入探讨了T2CPU架构下Linux操作系统对SMP(对称多处理器)系统的支持,包括SMP初始化、处理器分配策略以及处理器间中断机制。T2CPU拥有8个物理核,每个核上8个strand,构成64个虚拟处理器,形成SMP系统。文章分析了Linux内核在T2CPU上SMP支持的体系结构,如per_cpu域初始化、cpu分配策略和处理器间中断处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        多处理器系统(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的初始化:
       &nbsp在初始化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变量。
       &nbsp对于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域,这个函数我们不细讲。
       &nbsp第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
&nbsp;&nbsp;&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
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;以%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支持就讲完了。

SPARC                                                                              Sun UltraSPARC II处理器 SPARC,全称为“可扩充处理器架构”(Scalable Processor ARChitecture),是RISC微处理器架构之一。它最早于1985年由升阳电脑所设计,也是SPARC国际公司的注册商标之一。这家公司于1989年成立,其目的是向外界推广SPARC,以及为该架构进行符合性测试。此外该公司为了扩阔SPARC设计的生态系统,SPARC国际也把标准开放,并授权予多间生产商采用,包括德州仪器、Cypress半导体、富士通等。由于SPARC架构也对外完全开放,因此也出现了完全开放原始码的LEON处理器,这款处理器以VHDL语言写成,并采用LGPL授权。 SPARC架构原设计给工作站使用,及后应用在升阳、富士通等制造的大型SMP服务器上。而升阳开发的Solaris操作系统也是为SPARC设计的系统之一,除Solaris外,NeXTSTEP、Linux、FreeBSD、OpenBSD及NetBSD系统也提供SPARC版本。 现时最新版本的SPARC为第8及第9版,在2005年12月,升阳方面宣布其UltraSPARC T1处理器将采用开放原始码方式。 开源CPU--OpenSparc T1简介     FPGA级别 2006年3月,Sun宣布开源化其多核心UltraSparc T1 CPU的处理器设计,采用的是GNU通用公共许可证(GNU GPL license)。之前Sun已经公开了"Hypervisor"API规范,允许各公司将Linux、BSD 及其他操作系统移植到UltraSparc T1平台。 Sun是业界首家将复杂的硬件设计使用GNU GPL许可进行发布的公司,而此举也将为UltraSparc T1处理器增加曝光度,并吸引开发人员为该平台开发软硬件解决方案。 该硬件设计的开源发布包括64-bit UltraSparc T1的Verilog硬件描述语言源代码,验证套装和模拟模型,ISA规范及Solaris 10 OS虚拟镜像。T1处理器的代号为“Niagara”,于去年发布并应用于Sun的T1000/T2000服务器中。 Sun目前推出了4、6、8核心的CPU版本,且每核心最多支持4线程,即总共最多32线程。T1基于Sparc V9架构,每核心集成16KB指令缓存和8KB主数据缓存,整个处理器共享3MB L2缓存。“OpenSparc T1”芯片设计,验证套装,架构和性能模型工具已经发布在http://www.opensparc.net网站。Sun还发布了“Cool Tools”,其中包括优化多线程CPU性能的各种程序以及CMT编程及描绘工具。 OpenSparc T1处理器的主要特征包括: 8个Sparc V9处理核心,每核心4线程,共计32线程 每处理核心16KB一级指令缓存,共128KB; 每处理核心8KB一级数据缓存,共64KB; 3MB二级缓存,4-way bank,12向关联,各核心共享; 4个DDR2内存控制器,每通道位宽144bit,总带宽峰值25GB/s; IEEE754兼容浮点单元(FPU),各核心共享; J-Bus输入输出接口,峰值带宽2.56GB/s,128bit多元地址/数据复用总线。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值