sparc架构代码分析-setup_arch函数分析

本文深入剖析Sparc体系结构下Linux内核的初始化过程,涵盖setup_arch函数、显示控制台初始化、内存管理初始化、设备描述符初始化等关键环节。详细解读了内核参数处理、显示控制台工作原理、内存映射、trap表切换等核心技术点。

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

setup_arch函数做一下平台相关的基本的初始化,先把setup_arch函数的源代码贴出来,然后再分析,代码如下:

    282 void __init setup_arch(char **cmdline_p) 
    283 { 
    284         /* Initialize PROM console and command line. */ 
    285         *cmdline_p = prom_getbootargs(); 
    286         strcpy(boot_command_line, *cmdline_p); 
    287         parse_early_param(); 
    288 
    289         boot_flags_init(*cmdline_p); 
    290 #ifdef CONFIG_EARLYFB 
    291         if (btext_find_display()) 
    292 #endif 
    293                 register_console(&prom_early_console); 
    294 
    295         if (tlb_type == hypervisor) 
    296                 printk("ARCH: SUN4V\n"); 
    297         else 
    298                 printk("ARCH: SUN4U\n"); 
    299 
    300 #ifdef CONFIG_DUMMY_CONSOLE 
    301         conswitchp = &dummy_con; 
    302 #endif 
    303 
304         idprom_init(); 
305 
306         if (!root_flags) 
307                 root_mountflags &= ~MS_RDONLY; 
308         ROOT_DEV = old_decode_dev(root_dev); 
309 #ifdef CONFIG_BLK_DEV_RAM 
310         rd_image_start = ram_flags & RAMDISK_IMAGE_START_MASK; 
311         rd_prompt = ((ram_flags & RAMDISK_PROMPT_FLAG) != 0); 
312         rd_doload = ((ram_flags & RAMDISK_LOAD_FLAG) != 0); 
313 #endif 
314 
315         task_thread_info(&init_task)->kregs = &fake_swapper_regs; 
316 
317 #ifdef CONFIG_IP_PNP 
318         if (!ic_set_manually) { 
319                 int chosen = prom_finddevice ("/chosen"); 
320                 u32 cl, sv, gw; 
321 
322                 cl = prom_getintdefault (chosen, "client-ip", 0); 
323                 sv = prom_getintdefault (chosen, "server-ip", 0); 
324                 gw = prom_getintdefault (chosen, "gateway-ip", 0);
325                 if (cl && sv) { 
326                         ic_myaddr = cl; 
327                         ic_servaddr = sv; 
328                         if (gw) 
329                                 ic_gateway = gw; 
330 #if defined(CONFIG_IP_PNP_BOOTP) || defined(CONFIG_IP_PNP_RARP) 
331                         ic_proto_enabled = 0; 
332 #endif 
333                 } 
334         } 
335 #endif 
336 
337         /* Get boot processor trap_block[] setup.  */ 
338         init_cur_cpu_trap(current_thread_info()); 
339 
340         paging_init(); 
341 } 

第285行,看函数的名字就知道,是调用函数prom_getbootargs得到内核的启动参数放到* cmdline_p中,我们看一看,prom_getbootargs函数。

31 prom_getbootargs(void) 
32 { 
33         /* This check saves us from a panic when bootfd patches args. */ 
34         if (bootstr_info.bootstr_valid) 
35                 return bootstr_info.bootstr_buf; 
36         prom_getstring(prom_chosen_node, "bootargs", 
37                        bootstr_info.bootstr_buf, BARG_LEN); 
38         bootstr_info.bootstr_valid = 1; 
39         return bootstr_info.bootstr_buf; 
40 }

第33行bootstr_info是一个定义来专门保存内核启动参数的变量,它第一如下:

	struct { 
		       int bootstr_len; 
		       int bootstr_valid; 
		       char bootstr_buf[BARG_LEN]; 
	} bootstr_info = { 
		        .bootstr_len = BARG_LEN, 
	#ifdef CONFIG_CMDLINE 
		        .bootstr_valid = 1, 
		       .bootstr_buf = CONFIG_CMDLINE, 
	#endif 
	}; 

从该变量的定义可知定义的时候已经被初始化过了,它的第一个成员是启动参数的最大长度,第二个成员是一个标志,表示当前是否已经有了有效的启动参数,如果为1,则表示已经有了,第三个成员则用来保存具体的启动参数了。从它的初始化可以看出如果定义了 CONFIG_CMDLINE ,则在这里就给它附了启动参数
我们接着看第33行,如果 bootstr_info.bootstr_valid有效,即定义了 CONFIG_CMDLINE,则直接返回内核配置选项中配置的启动参数(make menuconfig)
第36行到39行,如果内核配置时没有配置启动参数,则调用 prom_getstring函数得到bootloader即启动引导程序传递给内核的参数。它实际上是调用hv接口函数得到hv设备节点树 的 prom_chosen_node的bootargs属性,这个属性就是内核启动参数。至于hv接口,在head.S中已经分析过,在head.S中,曾调用prom_init把hv接口封装成了c语言函数,之后就 能调用 prom_getstring等相关函数了。

我们接着回到setup_arch函数,
第286行,把得到的 启动参数放到 boot_command_line变量中
第287行,调用parse_early_param()函数对启动参数做一个初步分析,这个函数,在setup_arch函数调用完成后还会在start_kernel函数中再次被调用,到时候我们再具体分析它.
第289行,这一行同样是对命令行进行分析,调用boot_flags_init函数,该函数定义如下:

126 static void __init boot_flags_init(char *commands) 
127 { 
128         while (*commands) { 
129                 /* Move to the start of the next "argument". */ 
130                 while (*commands && *commands == ' ') 
131                         commands++; 
132 
133                 /* Process any command switches, otherwise skip it. */ 
134                 if (*commands == '\0') 
135                         break; 
136                 if (*commands == '-') { 
137                         commands++; 
138                         while (*commands && *commands != ' ') 
139                                 process_switch(*commands++); 
140                         continue; 
141                 } 
142                 if (!strncmp(commands, "mem=", 4)) { 
143                         /* 
144                          * "mem=XXX[kKmM]" overrides the PROM-reported 
145                          * memory size. 
146                          */ 
147                         cmdline_memory_size = simple_strtoul(commands + 4, 
148                                                              &commands, 0); 
149                         if (*commands == 'K' || *commands == 'k') { 
150                                 cmdline_memory_size <<= 10; 
151                                 commands++; 
152                         } else if (*commands=='M' || *commands=='m') { 
153                                 cmdline_memory_size <<= 20; 
154                                 commands++; 
155                         } 
156                 } 
157                 while (*commands && *commands != ' ') 
158                         commands++; 
159         } 
160 } 

该函数比较简单,就不逐行分析了,第130,131行,如果碰到命令行中的空字符,则计数器加一,第136到140行,如果碰到形如”-x”这种形式的参数,则调用 process_switch,第 142到155行,取出命令行参数中指定的内存大小,即形如”mem=xxM”的参数,把指定的内存大小保存在 cmdline_memory_size变量中。
该函数只关心内核参数中的两种参数,即内存大小和形如”-x”的参数,对”-x”的参数使用
process_switch函数进行处理,该函数定义如下:

96 static void __init process_switch(char c) 
97 { 
98         switch (c) { 
99         case 'd': 
100         case 's': 
101                 break; 
102         case 'h': 
103                 prom_printf("boot_flags_init: Halt!\n"); 
104                 prom_halt(); 
105                 break; 
106         case 'p': 
107                 /* Just ignore, this behavior is now the default.  */ 
108                 break; 
109         case 'P': 
110                 /* Force UltraSPARC-III P-Cache on. */ 
111                 if (tlb_type != cheetah) { 
112                         printk("BOOT: Ignoring P-Cache force option.\n"); 
113                         break; 
114                 } 
115                 cheetah_pcache_forced_on = 1; 
116                 add_taint(TAINT_MACHINE_CHECK); 
117                 cheetah_enable_pcache(); 
118                 break; 
119 
120         default: 
121                 printk("Unknown boot switch (-%c)\n", c); 
122                 break; 
123         } 
124 } 

看了这个函数,真正产生实际效果的是‘-P’参数,但是它只针对 tlb_type = cheetah的平 台,而我们的 tlb_type = hypervisor,所以这个函数对我们而言可以忽略。

显示控制台:
接着看setup_arch函数:
下面是初始化与显示器相关的显示控制台的。
第291行,初始化一个与显示器相关的控制台,这样就可以在显示器上打印信息了,当然这里的显示只能显示文本,不能显示图形,我们可以看看这个函数btext_find_display函数,该函数在arch/sparc/kernel/btext.c文件中,定义如下:

310 int __init btext_find_display(void)
311 {
312         unsigned int node;
313         char type[32];
314         int ret;
315 
316         node = prom_inst2pkg(prom_stdout);
317         if (prom_getproperty(node, "device_type", type, 32) < 0)
318                 return -ENODEV;
319         if (strcmp(type, "display"))
320                 return -ENODEV;
321 
322         ret = btext_initialize(node);
323         if (!ret) {
324                 btext_clearscreen();
325                 register_console(&btext_console);
326         }
327         return ret;
328 }

第316行,是prom前缀开头的函数,一看这种函数就是与OBP打交道的, 这类函数在prom_init函数后就可用了,是用来请求某个OBP服务的,这里就是根据 prom_stdout调用固件服务得到标准输出设备的节点号,prom_stdout是在prom_init函 数中赋值的,它实际上就是节点树的/chosen节点的”stdout”属性,对应的是标准输出 设备
第317行,调用OBP服务得到标准输出设备的设备类型,实际上就是设备节点 的”device_type”属性,如果没有就出错退出
第319行,判断标准输出设备是否是显示设备,如果不是,直接退出,因为这 里是要初始化显示控制台的,不显示设备就退出。
第320行,这个函数就是执行与显示相关的初始化,我们接下来看这个函数:

43 static int __init btext_initialize(unsigned int node)
44 {
45         unsigned int width, height, depth, pitch;
46         unsigned long address = 0;
47         u32 prop;
48 
49         if (prom_getproperty(node, "width", (char *)&width, 4) < 0)
50                 return -EINVAL;
51         if (prom_getproperty(node, "height", (char *)&height, 4) < 0)
52                 return -EINVAL;
53         if (prom_getproperty(node, "depth", (char *)&depth, 4) < 0)
54                 return -EINVAL;
55         pitch = width * ((depth + 7) / 8);
56 
57         if (prom_getproperty(node, "linebytes", (char *)&prop, 4) >= 0 &&
58             prop != 0xffffffffu)
59                 pitch = prop;
60 
61         if (pitch == 1)
62                 pitch = 0x1000;
63 
64         if (prom_getproperty(node, "address", (char *)&prop, 4) >= 0)
65                 address = prop;
66 
67         /* FIXME: Add support for PCI reg properties. Right now, only
68          * reliable on macs
69          */
70         if (address == 0)
71                 return -EINVAL;
72 
73         g_loc_X = 0;
74         g_loc_Y = 0;
75         g_max_loc_X = width / 8;
76         g_max_loc_Y = height / 16;
77         dispDeviceBase = (unsigned char *)address;
78         dispDeviceRowBytes = pitch;
79         dispDeviceDepth = depth == 15 ? 16 : depth;
80         dispDeviceRect[0] = dispDeviceRect[1] = 0;
81         dispDeviceRect[2] = width;
82         dispDeviceRect[3] = height;
83 
84         return 0;
85 }	

第49到53行,分别得到显示设备的分辨率和色深;
第55到62行,得到显示一行像素点所需的字节数,第55行先通过计算得到一个初始值,然后调用OBP服务从OBP中取得,如果能够取到,则使用从OBP中得到的值,否则使用初始值。
第64行,调用OBP服务得到显示设备的’address’属性,这个属性实际上就是显示设备的frambuffer的基地址,当使用显示设备输出时,只需要把像素数据写到frambuffer地址就行了。
第73行到83行,初始化一些全局变量,当使用显示设备输出时会用到这些变量,其中第73,74行的g_loc_X ,g_loc_Y变量实际上表示的是屏幕上光标的位置,第75行,g_max_loc_X表示一行可以显示多少个字符,76行 g_max_loc_Y表示一列可以显示多少个字符,从这两个变量可以看出一个字符占有8x16个像素点, dispDeviceRect数组是用来计算给定的像素点在frambuffer中的位置的,其中 dispDeviceRect[0], dispDeviceRect[1]是第一个像素点的x,y坐标, dispDeviceRect[2], dispDeviceRect[3]是最后一个像素点的坐标。
我们回到btext_find_display函数中来,
324行,清空屏幕,btext_clearscreen函数有兴趣的自行分析,它实际上是把frambuffer空间清0
第325行,注册我们这里实现的显示控制台,该控制台定一如下:

static struct console btext_console = { 
        .name   = "btext",
        .write  = btext_console_write,
        .flags  = CON_PRINTBUFFER | CON_ENABLED | CON_BOOT | CON_ANYTIME,
        .index  = 0,
};

当使用该控制台向显示设备输出字符时,会调用控制台定义中的write函数,它是在同一个文件中定义的,定义如下:

  297 static void btext_console_write(struct console *con, const char *s,
	298                                 unsigned int n)
	299 {
	300         btext_drawtext(s, n);
	301 }

该函数调用函数btext_drawtext:

188 static void btext_drawtext(const char *c, unsigned int len)
189 {
190         while (len--)
191                 btext_drawchar(*c++);
192 }

该函数调用 btext_drawchar输出字符串中的每一个字符:

141 void btext_drawchar(char c)
	142 {
	143         int cline = 0;
	144 #ifdef NO_SCROLL
	145         int x;
	146 #endif
	147         switch (c) {
	148         case '\b':
	149                 if (g_loc_X > 0)
	150                         --g_loc_X;
	151                 break;
	152         case '\t':
	153                 g_loc_X = (g_loc_X & -8) + 8;
	154                 break;
	155         case '\r':
	156                 g_loc_X = 0;
	157                 break;
	158         case '\n':
	159                 g_loc_X = 0;
	160                 g_loc_Y++;
	161                 cline = 1;
	162                 break;
	163         default:
	164                 draw_byte(c, g_loc_X++, g_loc_Y);
	165         }
	166         if (g_loc_X >= g_max_loc_X) {
	167                 g_loc_X = 0;
	168                 g_loc_Y++;
	169                 cline = 1;
	170         }
	171 #ifndef NO_SCROLL
	172         while (g_loc_Y >= g_max_loc_Y) {
	173                 scrollscreen();
	174                 g_loc_Y--;
	175         }
	176 #else
	177         /* wrap around from bottom to top of screen so we don't
	178            waste time scrolling each line.  -- paulus. */
	179         if (g_loc_Y >= g_max_loc_Y)
	180                 g_loc_Y = 0;
	181         if (cline) {
	182                 for (x = 0; x < g_max_loc_X; ++x)
	183                         draw_byte(' ', x, g_loc_Y);
	184         }
	185 #endif
	186 }

第148行到162行,如果是换行,回车等特殊字符的话值更新光标位置,而不把字符显示到屏幕上,
第164行,如果是常规字符,则调用draw_byte函数把字符显示到屏幕上:

194 static void draw_byte(unsigned char c, long locX, long locY)
195 {
196         unsigned char *base     = calc_base(locX << 3, locY << 4);
197         unsigned char *font     = &vga_font[((unsigned int)c) * 16];
198         int rb                  = dispDeviceRowBytes;
199 
200         switch(dispDeviceDepth) {
201         case 24:
202         case 32:
203                 draw_byte_32(font, (unsigned int *)base, rb);
204                 break;
205         case 15:
206         case 16:
207                 draw_byte_16(font, (unsigned int *)base, rb);
208                 break;
209         case 8:
210                 draw_byte_8(font, (unsigned int *)base, rb);
211                 break;
212         }
213 }

第196行,计算要显示的像素点在frambuffer中的位置
第197行,得到要显示的字符所代表信息在vga_font数组中的位置,要理解vga数组的作用,就要对字符是怎么在屏幕上显示的有所了解了。每个字符占有8x16个像素点,每行有8个像素点,每列有16个像素点,当字符在屏幕上显示时,必须点亮这8x16个像素点中的多个像素点从而在屏幕上显示出字符,以8位色深为例,每个像素点在frambuffer占由8位即一个字节的空间,由于只显示文本,所以显示时不用考虑颜色,那么该字节中全1就代表熄灭像素点,但是点亮像素点却并非全0而是0xF0。但是要显示的字符在它的8x16个像素点中要点亮那些像素点呢,这就跟字体相关了,当然我们这里的显示控制台只能显示一种字体,每个字符在该字体下要点亮哪些像素点就可以到该字体的字体库中去查找了,这里的字体库就是我们这里的vga_font数组了,每个字符在该数组中占有8x16位的数据,即16个8字节的数据,其中这每个字节的数据中的每一位就代表每行8个像素点中的一个像素点是点亮还是熄灭,而每个字符在vga_font数组中是按照ascii编码顺序存放的。
第201到211行,根据色深来调用不同的函数把字符显示到屏幕上,我们只看其中的
draw_byte_8函数:

281 static void draw_byte_8(unsigned char *font, unsigned int *base, int rb)
	282 {
	283         int l, bits;
	284         int fg = 0x0F0F0F0FUL;
	285         int bg = 0x00000000UL;
	286         unsigned int *eb = (int *)expand_bits_8;
	287 
	288         for (l = 0; l < 16; ++l)
	289         {
	290                 bits = *font++;
	291                 base[0] = (eb[bits >> 4] & fg) ^ bg;
	292                 base[1] = (eb[bits & 0xf] & fg) ^ bg;
	293                 base = (unsigned int *) ((char *)base + rb);
	294         }
	295 }

该函数的主体是一个for循环,该for循环的次数是16次,一看就知道每次循环就是显示该字符的每行8个像素点。
第190行,取出vga_font数组中代表该行的8个像素点的信息放到变量bits中。
第191行,从传入的参数可知,base(即要显示的像素点在framebuffer中的位置)变量是int型的指针,因此它每次能显示4个字节的信息,即每次显示4个像素点,每行8个像素点分两次显示完,即这里道德第291行和292行。每次显示4个像素点291行先显示前4个像素点,但是frambuffer里面存放的是每个像素点占有一个字节,这一个字节是全1或0xF0,那么4个字节的值是多少就可以计算出来了,但是计算出来显然比较麻烦,这里就引入了一个数组 expand_bits_8,4个像素点,其中每个像素点是点亮还是熄灭就2的4次放即16中情况,而 expand_bits_8就需要16个元素,每个元素就存放其中的一种情况,在 expand_bits_8数组中点亮像素点是按照全0来存放的,但是8位色深点量像素点却是0xF0,因此取出数组中的数据后需要进行一下处理。
第293行,把像素数据写入framebuffer中。
到这里一个像素点是怎么显示到屏幕上就解释清楚了,当然16位色深和32位色深的显示方法读者可自行分析,原理跟8位色深一样。这样draw_byte函数就分析完了。
我们再回到btext_drawchar函数中来,字符显示到屏幕上后,第166行到184行都是跟新光标位置的,其中有两中情况第166行到第169行是处理要换行的情况,后面的是要处理显示到屏幕上最后的位置要情况屏幕的情况。
到这里显示控制台就分析完了。

回到setup_arch函数中来。
我们看下一个函数idprom_init,它定义在arch/sparc/kernel/idprom.c文件中。定义如下:

88 void __init idprom_init(void)
	89 {
 	90         prom_get_idprom((char *) &idprom_buffer, sizeof(idprom_buffer));
 	91 
 	92         idprom = &idprom_buffer;
 	93 
 	94         if (idprom->id_format != 0x01)
 	95                 prom_printf("IDPROM: Warning, unknown format type!\n");
 	96 
 	97         if (idprom->id_cksum != calc_idprom_cksum(idprom))
 	98                 prom_printf("IDPROM: Warning, checksum failure (nvram=%x, calc=%x)!\n",
 	99                             idprom->id_cksum, calc_idprom_cksum(idprom));
	100 
	101         display_system_type(idprom->id_machtype);
	102 
	103         printk(KERN_WARNING "Ethernet address: %pM\n", idprom->id_ethaddr);
	104 }

这个函数的作用实际上是用来通过prom接口得到机器类型id的。
第90行,通过函数prom_get_idprom得到机器类型相关信息放到 idprom_buffer 结构变量中,这个函数就不具体分析了。这个函数就是取得节点树的根节的”idprom” 属性,然后放到idprom_buffer结构中。
第97行,对得到的idprom信息进行校验和校验。
第101行,显示机器类型信息。

我们接着看setup_arch函数:
第306,307行,root_flags变量在head_64.S文件中已经被初始化为1了,因此这条if分支是不会执行的。
第308行,ROOT_DEV初始化为0,ROOT_DEV是大写的,按照Linux的变量命名习惯,它看起来像是一个宏,但其实却是一个dev_t类型的变量,这个变量是个设备号,这里把它初始化为0。
第310行到312行,我们是没有定义的,不用去管。
第315行,把 init_task的thread_info结构的kregs成员赋值为&fake_swapper_regs,该成员是用来保存寄存器信息的。
第317行到335行,又是我们不用管的。
第338行,初始化当前CPU的trap_per_cpu变量,trap_per_cpu是每个virtual cpu都有的一个变量,里面保存有cpu处理trap时需要用到的一些信息。这里是把trap_per_cpu变量的thread成员置成当前进程的thread_info结构, 把pgd_paddr成员置0。
第340行,paging_init函数,该函数会为内核建立页表,并为设备资源建立设备节点树,我们看这个函数:
该函数是在文件arch/sparc/mm/init_64.c文件中定义的。

1683 void __init paging_init(void)
1684 {
1685         unsigned long end_pfn, shift, phys_base;
1686         unsigned long real_end, i;
1712 
1713         kern_base = (prom_boot_mapping_phys_low >> 22UL) << 22UL;
1714         kern_size = (unsigned long)&_end - (unsigned long)KERNBASE;
1715 
1716         /* Invalidate both kernel TSBs.  */
1717         memset(swapper_tsb, 0x40, sizeof(swapper_tsb));
1718 #ifndef CONFIG_DEBUG_PAGEALLOC
1719         memset(swapper_4m_tsb, 0x40, sizeof(swapper_4m_tsb));
1720 #endif
1721 
1722         if (tlb_type == hypervisor)
1723                 sun4v_pgprot_init();
1724         else
1725                 sun4u_pgprot_init();
1726 
1727         if (tlb_type == cheetah_plus ||
1728             tlb_type == hypervisor)
1729                 tsb_phys_patch();
1730 
1731         if (tlb_type == hypervisor) {
1732                 sun4v_patch_tlb_handlers();
1733                 sun4v_ktsb_init();
1734         }
1735 
1736         lmb_init();
1737 
1746         read_obp_translations();
1747         read_obp_memory("reg", &pall[0], &pall_ents);
1748         read_obp_memory("available", &pavail[0], &pavail_ents);
1749         read_obp_memory("available", &pavail[0], &pavail_ents);
1750 
1751         phys_base = 0xffffffffffffffffUL;
1752         for (i = 0; i < pavail_ents; i++) {
1753                 phys_base = min(phys_base, pavail[i].phys_addr);
1754                 lmb_add(pavail[i].phys_addr, pavail[i].reg_size);
1755         }
1756 
1757         lmb_reserve(kern_base, kern_size);
1758 
1759         find_ramdisk(phys_base);
1760 
1761         lmb_enforce_memory_limit(cmdline_memory_size);
1762 
1763         lmb_analyze();
1764         lmb_dump_all();
1765 
1766         set_bit(0, mmu_context_bmap);
1767 
1768         shift = kern_base + PAGE_OFFSET - ((unsigned long)KERNBASE);
1769 
1770         real_end = (unsigned long)_end;
1771         num_kernel_image_mappings = DIV_ROUND_UP(real_end - KERNBASE, 1 << 22);
1772         printk("Kernel: Using %d locked TLB entries for main kernel image.\n",
1773                num_kernel_image_mappings);
1774 
1775         /* Set kernel pgd to upper alias so physical page computations
1776          * work.
1777          */
1778         init_mm.pgd += ((shift) / (sizeof(pgd_t)));
1779 
1780         memset(swapper_low_pmd_dir, 0, sizeof(swapper_low_pmd_dir));
1781 
1782         /* Now can init the kernel/bad page tables. */
1783         pud_set(pud_offset(&swapper_pg_dir[0], 0),
1784                 swapper_low_pmd_dir + (shift / sizeof(pgd_t)));
1785 
1786         inherit_prom_mappings();
1787 
1788         init_kpte_bitmap();
1789 
1790         /* Ok, we can use our TLB miss and window trap handlers safely.  */
1791         setup_tba();
1792 
1793         __flush_tlb_all();
1794 
1795         if (tlb_type == hypervisor)
1796                 sun4v_ktsb_register();
1797 
1798         prom_build_devicetree();
1799         of_populate_present_mask();
1800 #ifndef CONFIG_SMP
1801         of_fill_in_cpu_data();
1802 #endif
1803 
1804         if (tlb_type == hypervisor) {
1805                 sun4v_mdesc_init();
1806                 mdesc_populate_present_mask(cpu_all_mask);
1807 #ifndef CONFIG_SMP
1808                 mdesc_fill_in_cpu_data(cpu_all_mask);
1809 #endif
1810         }
1811 
1812         /* Once the OF device tree and MDESC have been setup, we know
1813          * the list of possible cpus.  Therefore we can allocate the
1814          * IRQ stacks.
1815          */
1816         for_each_possible_cpu(i) {
1817                 /* XXX Use node local allocations... XXX */
1818                 softirq_stack[i] = __va(lmb_alloc(THREAD_SIZE, THREAD_SIZE));
1819                 hardirq_stack[i] = __va(lmb_alloc(THREAD_SIZE, THREAD_SIZE));
1820         }
1821 
1822         /* Setup bootmem... */
1823         last_valid_pfn = end_pfn = bootmem_init(phys_base);
1824 
1825 #ifndef CONFIG_NEED_MULTIPLE_NODES
1826         max_mapnr = last_valid_pfn;
1827 #endif
1828         kernel_physical_mapping_init();
1829 
1830         {
1831                 unsigned long max_zone_pfns[MAX_NR_ZONES];
1832 
1833                 memset(max_zone_pfns, 0, sizeof(max_zone_pfns));
1834 
1835                 max_zone_pfns[ZONE_NORMAL] = end_pfn;
1836 
1837                 free_area_init_nodes(max_zone_pfns);
1838         }
1839 
1840         printk("Booting Linux...\n");
1841 }

建立内核页表:
第1713行,prom_boot_mapping_phys_low变量我们在head_64.S文件的分析中见过,该变量的值是内核映像的物理基地址,这里把它进行4M对齐的处理后存放在kern_base变量中。
第1714行,得到内核映像的大小。
第1717行,把swapper_tsb中的每个字节都置为0x40,swapper_tsb是在head_64.S文件中静态分配的,它的大小是32K,swapper_tsb是干什么用的呢?
CPU的每个虚拟处理器上都集成了MMU,MMU是用来把虚拟地址转换程物理地址的,具体到我们这里的sparc结构,在内核中,MMU是把虚拟地址转换程实地址的,但是为了表述的方便,在内核中我们一直把实地址称为物理地址的,实际上对内核而言,两者没区别,我们以后也还是以物理地址称之。MMU把虚拟地址转换成物理地址需要页表的帮助,即通过虚拟地址查找页表从而得到与该虚拟地址相关关联的物理地址,但是为了提高地址转换的速率,MMU实现上采用了缓存的机制,MMU在硬件上一般集成了TLB,最近最常使用的页表项都缓存在TLB上,这样地址转换时就可以直接使用TLB中缓存的表项来进行转换,当TLB没有与要转换地址相关的表项时(即TLB miss),如果直接从页表中查找相关表现再缓存到TLB中,效率仍然是不高的,因此在有些实现中,在TLB和页表直接存在这TSB,TSB也位于内存中,它可以看作是TLB与页表之间的告诉缓存,当TLB miss时,先从TSB中查找相关表现,如果发生TSB miss,再从页表中查找。当然这些机制不同的平台具有不同的具体实现,就T2处理器来说,它在内存中有多个TSB,当TLB miss时,可以同时在多个TSB中查找。我们这里不用关心T2 MMU实现的具体物理细节,因为内核中对MMU的操作都是通过hypervisor调用接口调用hypervisor代码来实现的,所以物理实现细节是hypervisor代码关心的事情。但是内核需要知道TSB表项的格式:

Bit	 Field	 Description
Tag-63:48	context_id 	The 16-bit context ID associated with the TTE
Tag– 47:42 	— 	These bits must be zero for a tag match
Tag– 41:0 	va 	要转换的虚拟地址的63:22位
Data – 63 	v	有效位,如果该位为0,则说明表项是无效的。
Data – 62 nfo 如果该位置位,则该表项所映射的物理页可以被无故障访问,无故障访问可以实现一些常规访问无法提供的功能,比如常规访问当访问空指针时就会产生一个错误,但无故障访问却可以安全的访问,利用这中特性,在程序调试和编译优化中是十分有效的,不过我们不用关心
Data – 61:56 	soft2 	这几位的作用由软件定义,MMU硬件是不关心的
Data – 55:13 	taddr 	即映射的物理地址
Data – 12 ie 大小端反转位,即如果当前系统访问内存采用大端格式,那么如果该位置位,则访问该表现所映射的页是会使用小端格式访问
Data – 11 	 e 	Side effect
Data – 10	cp	cp和cv位标志该页是否可缓存(cachable)的,其中cp是cacheable-in-physically-indexed-cache
Data – 9 	cv	cacheable-in-virtually-
indexed-cache
Data – 8	p	如果该位置位,则只有privileged and hyperprivileged software
能够访问该页
Data – 7 	ep 	表示该页是否拥有可执行权限
Data – 6 	w	表示该页是否是可写的
Data – 5:4 	soft 	这几位的作用由软件定义
Data – 3:0 	size	页大小:
sz                                    Page Size
0000                                8 Kbyte
0001                               64 Kbyte
0010                              512 Kbyte
0011                               4 Mbyte
010                                32 Mbyte
0101                               256 Mbyte
0110                              2 Gbyte
0111                              16 Gbyte
1000-111                      Reserved

TSB中的每一个表项都有两个64位数据,分别是tag和data,其中tag是用来匹配的,如果某个内存访问匹配了tag字段,则它的地址转换就是通过该表现进行的,其中主要匹配两个字段即contex_id和虚拟地址的63:22位,其中contex_id是sparc体系结构中用来区分不同的虚拟地址空间的。 Data字段的内容则是TSB表项的主要目的了,它里面就存放了虚拟页所映射的物理页的物理地址,除了物理地址外,它还存放了一些所映射页的属性,包括是否可缓存,是否可写等。
这里1717行的swapper_tsb就是上面所说的TSB,不过T2中可以同时存在多个TSB,swapper_tsb是专门用来处理内核地址转换的TSB。
第1718到1720行,与debug相关的,我们不用管。
第1722到1725行,我们的系统里面走的是sun4v_pgprot_init分支,这个函数的内容我们就不贴出来了,这个函数就是初始化了一些变量,这些变量是干什么的呢,我们上面说页表项中不仅存有虚拟页所映射物理页的物理地址外,还有所映射页的访问属性。当然在不同的场合映射页具有不同的属性,比如内核应该具有可读可写可执行权限,且是可缓存的,再如系统中一些特殊的场合要求页是只读的。这里就是为这些不同场合的页属性预定义一些标志页属性的属性值放在protection_map数组中,当要为页建立页表时可直接使用。
第1729行,tsb_phys_patch()函数根据处理器的类型信息实现处理器相关的访问TSB的操作,相当于打patch,类似的情形我们在head_64.S文件的分析中遇到过,这里就不一行行分析了。
第1732行,sun4v_patch_tlb_handlers也相当于打patch,不过它实际上完成的工作是把处理器类型相关的与处理地址转换的trap的trap handler(trap处理函数)安装的privilged模式下的trap table中去。

第1733行,sun4v_ktsb_init初始化内核TSB,它定义于同一文件中:

1609 static void __init sun4v_ktsb_init(void)
1610 {
1611         unsigned long ktsb_pa;
1612 
1613         /* First KTSB for PAGE_SIZE mappings.  */
1614         ktsb_pa = kern_base + ((unsigned long)&swapper_tsb[0] - KERNBASE);
1615 
1616         switch (PAGE_SIZE) {
1617         case 8 * 1024:
1618         default:
1619                 ktsb_descr[0].pgsz_idx = HV_PGSZ_IDX_8K;
1620                 ktsb_descr[0].pgsz_mask = HV_PGSZ_MASK_8K;
1621                 break;
1622 
1623         case 64 * 1024:
1624                 ktsb_descr[0].pgsz_idx = HV_PGSZ_IDX_64K;
1625                 ktsb_descr[0].pgsz_mask = HV_PGSZ_MASK_64K;
1626                 break;
1627 
1628         case 512 * 1024:
1629                 ktsb_descr[0].pgsz_idx = HV_PGSZ_IDX_512K;
1630                 ktsb_descr[0].pgsz_mask = HV_PGSZ_MASK_512K;
1631                 break;
1632 
1633         case 4 * 1024 * 1024:
1634                 ktsb_descr[0].pgsz_idx = HV_PGSZ_IDX_4MB;
1635                 ktsb_descr[0].pgsz_mask = HV_PGSZ_MASK_4MB;
1636                 break;
1637         };
1638 
1639         ktsb_descr[0].assoc = 1;
1640         ktsb_descr[0].num_ttes = KERNEL_TSB_NENTRIES;
1641         ktsb_descr[0].ctx_idx = 0;
1642         ktsb_descr[0].tsb_base = ktsb_pa;
1643         ktsb_descr[0].resv = 0;
1644 
1659 }

可以看出这个函数并没有为 swapper_tsb的每一项赋值,而是初始化了一个struct hv_tsb_descr 结构体变量ktsb_descr,该结构是hypervisor用来配置MMU的TSB的,这个函数把内核TSB swapper_tsb与struct hv_tsb_descr结构关联起来,当初始化号内核页表后,就可以调用hypervisor服务通过struct hv_tsb_descr配置TSB了。
回到paging_init函数,
第1736行,初始化lmb,这里就要提到lmb机制了,lmb是系统启动时Linux提供的一种简单的内存管理机制,它维护了一个全局变量struct lmb lmb,里面包含有系统保留的和可用的物理内存,当然这些物理内存可能在地址上并不是连续的而是分成一片一片的,其中每一片的物理基地址和大小在lmb中都有保存,且他们是按照物理地址从小到达排序,lmb机制的实现在文件lib/lmb.c中,关于它的实现由于并不是体系结构相关的我们这里并不讲解,有兴趣的可以自行分析。这一行就是初始化lmb
第1746行,read_obp_translations函数,定义于同一文件中:

454 static void __init read_obp_translations(void)
455 {
456         int n, node, ents, first, last, i;
457 
458         node = prom_finddevice("/virtual-memory");
459         n = prom_getproplen(node, "translations");
460         if (unlikely(n == 0 || n == -1)) {
461                 prom_printf("prom_mappings: Couldn't get size.\n");
462                 prom_halt();
463         }
464         if (unlikely(n > sizeof(prom_trans))) {
465                 prom_printf("prom_mappings: Size %Zd is too big.\n", n);
466                 prom_halt();
467         }
468 
469         if ((n = prom_getproperty(node, "translations",
470                                   (char *)&prom_trans[0],
471                                   sizeof(prom_trans))) == -1) {
472                 prom_printf("prom_mappings: Couldn't get property.\n");
473                 prom_halt();
474         }
475 
476         n = n / sizeof(struct linux_prom_translation);
477 
478         ents = n;
479 
480         sort(prom_trans, ents, sizeof(struct linux_prom_translation),
481              cmp_ptrans, NULL);
482 
483         /* Now kick out all the non-OBP entries.  */
484         for (i = 0; i < ents; i++) {
485                 if (in_obp_range(prom_trans[i].virt))
486                         break;
487         }
488         first = i;
489         for (; i < ents; i++) {
490                 if (!in_obp_range(prom_trans[i].virt))
491                         break;
492         }
493         last = i;
494 
495         for (i = 0; i < (last - first); i++) {
496                 struct linux_prom_translation *src = &prom_trans[i + first];
497                 struct linux_prom_translation *dest = &prom_trans[i];
498 
499                 *dest = *src;
500         }
501         for (; i < ents; i++) {
502                 struct linux_prom_translation *dest = &prom_trans[i];
503                 dest->virt = dest->size = dest->data = 0x0UL;
504         }
505 
506         prom_trans_ents = last - first;
507 
508         if (tlb_type == spitfire) {
509                 /* Clear diag TTE bits. */
510                 for (i = 0; i < prom_trans_ents; i++)
511                         prom_trans[i].data &= ~0x0003fe0000000000UL;
512         }
513 }

分析这个函数之前先解释一下这个函数的作用,在sparc体系结构下,hypervisor是运行于hyper-privilged模式下的,它负责实地址到物理地址的转换。而OBP(启动引导程序)和Linux是运行在privilged模式下的,他们使用的是虚拟地址,负责虚拟地址到实地址的转换。所以在系统启动进入Linux之前,OBP运行时也会使用虚拟地址,所以在进入Linux之前,有些物理地址已经被OBP映射到虚拟地址了,进入Linux后,这些已有的映射关系有的是不需要的,但有的需要保留,判断的标准就是映射的虚拟地址空间是否位于为OBP保留的虚拟地址范围内。
第458到475行,调用OBP服务得到OBP节点树的/virtual-memory节点的translations属性放到数组prom_trans中,这样OBP所维护的地址映射关系就保存到数组prom_trans中了。
prom_trans数组中每个元素的定义如下:

 struct linux_prom_translation {
          unsigned long virt;
          unsigned long size;
          unsigned long data;
  };

其中virt成员代表映射的虚拟地址,size成员代表映射的内存块的大小,data成员代表映射的物理地址。

第476行,478行,得到prom_trans数组中元素的个数
第480行,把prom_trans数组元素排序,按其每个元素的virt成员由小到大排序。
第484行到493行,找出prom_trans数组中位于为OBP保留的虚拟地址范围内的映射关系。
第495到505行,把需要OBP保留的映射关系移到OBP保留的虚拟地址范围数组中的前几个元素中来,并把其他元素清0
第506行,把prom_trans数组的实际有效元素的个数存入变量prom_trans_ents中
回到paging_init函数中来,
第1747到1749行,这3行都是调用read_obp_memory函数,read_obp_memory函数就不贴出来一行行的分析了,看懂了上面的read_obp_translations函数,这个函数是很容易看懂的。
其中第1747行实际上是得到OBP节点树的”/memory“节点的“reg”属性放到数组pall中,第1748行和1749行得到OBP节点树的”/memory“节点的“available”属性放到数组pavail中,pall数组和pavil数组的元素都是struct linux_prom64_registers结构类型的,该结构定义如下:

struct linux_prom64_registers {
         unsigned long phys_addr;
         unsigned long reg_size;
 };

该结构实际上就标志了一个内存块,其中第1747行通过”reg”属性得到的pall数组标志的内存块是已经被OBP或者hypervisor保留使用的,而通过“available”得到的pavail数组所标志的内存区域是Linux可以使用的内存,前面提到lmb管理的系统可用的物理内存实际上就是这里的pavail数组中的内存区域而不包括pall数组中的内存区域。
第1752到1755行,把pavail数组中的每一块内存区域都加入到lmb中去,并通过比较得到Linux可以使用的内存区域的最小物理地址保存到phys_base变量中。
1757行,把内核映像使用的内存加入的lmb维护的保留的内存中去。
第1759行,find_ramdisk函数:

697 static void __init find_ramdisk(unsigned long phys_base)
698 {
699 #ifdef CONFIG_BLK_DEV_INITRD
700         if (sparc_ramdisk_image || sparc_ramdisk_image64) {
701                 unsigned long ramdisk_image;
702 
703                 /* Older versions of the bootloader only supported a
704                  * 32-bit physical address for the ramdisk image
705                  * location, stored at sparc_ramdisk_image.  Newer
706                  * SILO versions set sparc_ramdisk_image to zero and
707                  * provide a full 64-bit physical address at
708                  * sparc_ramdisk_image64.
709                  */
710                 ramdisk_image = sparc_ramdisk_image;
711                 if (!ramdisk_image)
712                         ramdisk_image = sparc_ramdisk_image64;
713 
714                 /* Another bootloader quirk.  The bootloader normalizes
715                  * the physical address to KERNBASE, so we have to
716                  * factor that back out and add in the lowest valid
717                  * physical page address to get the true physical address.
718                  */
719                 ramdisk_image -= KERNBASE;
720                 ramdisk_image += phys_base;
 721 
722                 numadbg("Found ramdisk at physical address 0x%lx, size %u\n",
723                         ramdisk_image, sparc_ramdisk_size);
724 
725                 initrd_start = ramdisk_image;
726                 initrd_end = ramdisk_image + sparc_ramdisk_size;
727 
728                 lmb_reserve(initrd_start, sparc_ramdisk_size);
729 
730                 initrd_start += PAGE_OFFSET;
731                 initrd_end += PAGE_OFFSET;
732         }
733 #endif
734 }

ramdisk是一个内存文件系统镜像,它里面保存了许多系统启动时需要用到的模块,ramdisk的地址是在head_64.S文件中静态定义的,保存在变量sparc_ramdisk_image64中,
sparc_ramdisk_image64中保存的是虚拟地址。
第719,720行,根据虚拟地址计算计算处ramdisk的物理地址
第725,726行,把randisk的物理起始地址和结束地址保存在initrd_start和initrd_end变量中。
第718行,把ramdisk所使用的内存同样加入lmb所维护的保留内存中去。
第730,731行,计算处ramdisk的起始虚拟地址和结束地址更新到initrd_start和initrd_end变量中去。这里得到的虚拟地址和sparc_ramdisk_image64中保存的虚拟地址是不一样的。我们知道系统启动过程中内核所在的那段物理地址空间进行了两次虚拟地址映射,一次是映射到从0开始的4M大小的虚拟地址空间,一次是映射到Linux正常执行时的虚拟地址空间,在32位体系结构中该虚拟地址空间是3G到4G之间的虚拟地址空间。这里sparc_ramdisk_image64就是从0开始的那段4M的虚拟地址空间中的地址。

回到paging_init函数中去,
第1761行,有时候内核参数会指定使用的内存的大小,如果lmb中可用的内存大于内核参数中指定的内存大小的话,则把多出的部分截去不用
第1763行,计算出Linux可用内存的总大小
第1764行,打印处系统内存信息。
第1766行,把contex_id的位图的所有位置0,contex_id是用来区分不同的虚拟地址空间的,但是哪一个虚拟地址空间使用哪一个contex_id则是没有限制的,因此使用一个位图来管理contex_id的分配。
第1768行,计算处一个shift,该shift实际上是用来在系统启动时内核映像的两次虚拟地址映射的地址之间进行转换的,从0开始的4M大小的虚拟地址空间中的虚拟地址加上shift则得到Linux正常执行时相应物理地址的虚拟地址。
第1771行,要想理解num_kernel_image_mappings变量的作用,就又要解释一下背景知识了,在Linux的执行过程中,在内核空间同时存在者两中虚拟地址映射关系,我们还是以32位的4G大小的虚拟地址空间为例,一种映射关系是把从物理地址开始的896M的物理内存平滑的线性的映射到虚拟地址3G到3G+896M的地址空间中,这种映射关系是固定的,一段映射完就不再改变,叫做线性映射,另外一种是通过ioremap和vmalloc建立的映射关系,这种映射是不确定,经常需要改变。对于前一种映射关系,由于它固定不变的特定,可以对它做一些优化,比如可以使用比较大的内存页映射,这样可以减少页表项,由于其映射关系一直不变,可以把它的表项锁定到TLB中,保证它的表项一直存在于TLB中不被换出。
这里的num_kernel_image_mappings变量就是计算出内核映像可以映射的4M页的个数即表项数,同时把内核映像的映射关系锁定到TLB里,注意这里只是内核映像的线性地址映射关系,而不是整个896M的内核空间映射关系,主要原因是TLB空间很小,而且还要留出一部分缓存其他的地址映射,且内核空间中最常使用的就是内核映像,其他的地址空间很少使用,没必要缓存。DIV_ROUND_UP的宏的作用实际上就是把real_end - KERNBASE除以1 << 22得到内核映像可以分成多少个4M的大小。
第1778行,init_mm是struct mm_struct类型的变量,该类型的变量叫做内存描述符,每个进程都有一个内存描述符,它记录了进程内存使用的所用信息,其中它的pgd字段则保存了进程也表的页全局目录的地址,而init_mm则是0号进程init_task的内存描述符号,这条语句有什么作用,后面再说。
第1780行,把swapper_low_pmd_dir,数组清零
第1783行,该行的pud_set,pud_offset都是与页表项相关的宏,这些宏我们就不分析了,这条语句的实际效果就是swapper_pg_dir[0]=swapper_low_pmd_dir+ + (shift / sizeof(pgd_t))。其中的shift变量前面已经说过,它是在内核的两种虚拟地址空转换的,这行实际上是把swapper_pg_dir页全局目录的第0个表项只为 swapper_low_pmd_dir的虚拟地址作移11位,由此可见swapper_low_pmd_dir是页表的第二个目录。
第1786行,inherit_prom_mappings函数实际上是为内核映像重新映射虚拟地址空间的,它实际上是调用remap_kernel函数,我们看remap_kernel函数:

530 static void __init remap_kernel(void)
531 {
532         unsigned long phys_page, tte_vaddr, tte_data;
533         int i, tlb_ent = sparc64_highest_locked_tlbent();
534 
535         tte_vaddr = (unsigned long) KERNBASE;
536         phys_page = (prom_boot_mapping_phys_low >> 22UL) << 22UL;
537         tte_data = kern_large_tte(phys_page);
538 
539         kern_locked_tte_data = tte_data;
540 
541         /* Now lock us into the TLBs via Hypervisor or OBP. */
542         if (tlb_type == hypervisor) {
543                 for (i = 0; i < num_kernel_image_mappings; i++) {
544                         hypervisor_tlb_lock(tte_vaddr, tte_data, HV_MMU_DMMU);
545                         hypervisor_tlb_lock(tte_vaddr, tte_data, HV_MMU_IMMU);
546                         tte_vaddr += 0x400000;
547                         tte_data += 0x400000;
548                 }
549         } else {
550                 for (i = 0; i < num_kernel_image_mappings; i++) {
551                         prom_dtlb_load(tlb_ent - i, tte_data, tte_vaddr);
552                         prom_itlb_load(tlb_ent - i, tte_data, tte_vaddr);
553                         tte_vaddr += 0x400000;
554                         tte_data += 0x400000;
555                 }
556                 sparc64_highest_unlocked_tlb_ent = tlb_ent - i;
557         }
558         if (tlb_type == cheetah_plus) {
559                 sparc64_kern_pri_context = (CTX_CHEETAH_PLUS_CTX0 |
560                                             CTX_CHEETAH_PLUS_NUC);
561                 sparc64_kern_pri_nuc_bits = CTX_CHEETAH_PLUS_NUC;
562                 sparc64_kern_sec_context = CTX_CHEETAH_PLUS_CTX0;
563         }
564 }

remap_kernel函数为内核映像建立虚拟地址映射实际上没有田重任何页表项或者TSB表项,它就是用我们前面提到的num_kernel_image_mappings把地址映射关系缩存到TLB中。
第535行,把内核虚拟基地址放到变量tte_vaddr 中,从这里可以看出这里映射的虚拟地址还是从0开始的虚拟地址而不是从3G开始的虚拟地址
第536行,tte_vaddr虚拟地址所对应的物理地址放到变量phys_page中,是内核映像的物理基地址,这里把它转换程4M对齐的地址,因为TLB锁存的映射关系使用的是4M大小的页。
第537行,设置TLB表项的data字段,放到tte_data变量中,实际上它是由phys_page和上文提到的页属性组成的,至于内核页有什么属性,请自行分析吧,可以确定的是页大小设置的是4M大小
第542到464行,的if分支语句我们只需要看hypervisor那个分支就行了,即543到547行的for循环,它是循环num_kernel_image_mappings次为内核映像的每个4M大小页建立映射关系并调用hypervisor服务进行TLB锁存。
该函数执行完后,内核映像就映射完了。

回到paging_init函数中来,
第1788行,这个函数就不分析了,它实际上是把前文提到的pall数组中的内存即不能被Linux使用的内存在kpte_linear_bitmap内存位图中置位,至于这个位图有什么作用kpte_linear_bitmap,等遇到的时候再说吧。
第1791行,setup_tba焊说是在head_64.S文件中定义的,当时分析这个文件时,并没用分析到这个函数,现在来看看这个函数:

786         .globl  setup_tba
787 setup_tba:
788         save    %sp, -192, %sp
789 
790         /* The boot processor is the only cpu which invokes this
791          * routine, the other cpus set things up via trampoline.S.
792          * So save the OBP trap table address here.
793          */
794         rdpr    %tba, %g7
795         sethi   %hi(prom_tba), %o1
796         or      %o1, %lo(prom_tba), %o1
797         stx     %g7, [%o1]
798 
799         call    setup_trap_table
800          nop
801 
802         ret
803          restore
804 sparc64_boot_end:	

看这个函数的名字就知道这个函数是设置Linux的trap table的。
第788行,更新寄存器窗口,这一行对我们的理解不重要。
第194行,读取tba寄存器的值到g7中,该寄存器里面保存的privileged模式下trap table 的虚拟基地址,进入Linux之前,一直在OBP中执行,因此这里实际上是把OBP的trap table的虚拟地址保存的g7中
第795行到797行,把g7的值保存到变量 prom_tba
这几条语句实际上是把OBP的trap talbe的地址保存到变量 prom_tba,这是很好理解的,因为进入内核后要使用Linux自己的trap table,所以把OBP的trap talbe 的地址保存起来。
第799行,调用setup_trap_table
函数。
第802行,返回到paging_init函数中去。
可以看出setup_tba
后的主要工作是有函数 setup_trap_table
完成的。

678         .globl  setup_trap_table
679 setup_trap_table:
680         save    %sp, -192, %sp
681 
682         /* Force interrupts to be disabled. */
683         rdpr    %pstate, %l0
684         andn    %l0, PSTATE_IE, %o1
685         wrpr    %o1, 0x0, %pstate
686         rdpr    %pil, %l1
687         wrpr    %g0, PIL_NORMAL_MAX, %pil

上面这一段是禁止中断,就不细分析了

689         /* Make the firmware call to jump over to the Linux trap table.  */
690         sethi   %hi(is_sun4v), %o0
691         lduw    [%o0 + %lo(is_sun4v)], %o0
692         brz,pt  %o0, 1f
693          nop

这一段实际上判断 is_sun4v变量的值,如果为0,就跳到标记1处执行,显然我们这里是不会跳转的。

695         TRAP_LOAD_TRAP_BLOCK(%g2, %g3)
696         add     %g2, TRAP_PER_CPU_FAULT_INFO, %g2
697         stxa    %g2, [%g0] ASI_SCRATCHPAD

第695行的宏我们已经见过多次了,它就是得到当前虚拟处理器的trap_block结构的地址,放在g2中
第696,697行,把trap_block变量的fault_info成员存入虚拟处理器的地址为0的 SCRATCHPAD寄存器中。这样在以后需要的时候可以快速的从SCRATCHPAD寄存器中取出它的值,我们在中断处理中可以看到。

703         sethi   %hi(KERNBASE), %g3
704         sub     %g2, %g3, %g2
705         sethi   %hi(kern_base), %g3
706         ldx     [%g3 + %lo(kern_base)], %g3
707         add     %g2, %g3, %o1
708         sethi   %hi(sparc64_ttable_tl0), %o0

g2里面存放的是当前虚拟处理器trap_block变量的fault_info的虚拟地址
第703,704行,fault_info的虚拟地址减去 KERNBASE放到g2中
第705,706行,kern_base的值放到g3中
第707行,g3与g2相加放到%o0中
这几行所这些的操作实际上是%o1=fault_info- KERNBASE+ kern_base,实际上是把 fault_info的物理地址放到%o1中
第708行, 把变量 sparc64_ttable_tl0的值放在%o0中, sparc64_ttable_tl0看名字就知道是trap=0时,trap table的虚拟基地址

710         set     prom_set_trap_table_name, %g2
711         stx     %g2, [%sp + 2047 + 128 + 0x00]
712         mov     2, %g2
713         stx     %g2, [%sp + 2047 + 128 + 0x08]
714         mov     0, %g2
715         stx     %g2, [%sp + 2047 + 128 + 0x10]
716         stx     %o0, [%sp + 2047 + 128 + 0x18]
717         stx     %o1, [%sp + 2047 + 128 + 0x20]
718         sethi   %hi(p1275buf), %g2
719         or      %g2, %lo(p1275buf), %g2
720         ldx     [%g2 + 0x08], %o1
721         call    %o1
722          add    %sp, (2047 + 128), %o0	

这一段应该很熟系把,它实际上是调用OBP接口服务函数调用OBP服务,它实际上是把参数放到栈中,然后在OBP中会从栈中取处参数,在head_64.S的分析中已经分析过这种调用,这里就不分析了。它的执行效果相当于:
prom_set_trap_talbe(2,0,fault_info,sparc64_ttable_tl0);
很显然,服务函数完成后,privliged mode的trap talbe的虚拟基地址就设置成 sparc64_ttable_tl0了,这样再发生trap时,就要进入Linux处理了。其中第3个参数 fault_info的定义如下:

struct hv_fault_status {
  unsigned long           i_fault_type;
  unsigned long           i_fault_addr;
  unsigned long           i_fault_ctx;
  unsigned long           i_reserved[5];
  unsigned long           d_fault_type;
  unsigned long           d_fault_addr;
  unsigned long           d_fault_ctx;
  unsigned long           d_reserved[5];
 };

该结构里面保存了指令MMU或数据MMU发生页故障时保存的故障类型地址,contex_id信息,这里设置后,当发生故障时,Linux就可一从里面取得这些信息

724         ba,pt   %xcc, 2f
725 	   nop

第724行,跳转到2处执行

742 2:      sethi   %hi(sparc64_kern_pri_context), %g3
743         ldx     [%g3 + %lo(sparc64_kern_pri_context)], %g2
744 
745         mov             PRIMARY_CONTEXT, %g1

这里就是标记2处了
第702,703行,把 sparc64_kern_pri_context的值放入g2中,看这个变量的名字就知道他是内核执行时标志内核虚拟地址空间的 的contex_id,这个变量在前面一直没有附过值,这里应该是0。
第745行,把虚拟处理器的 PRIMARY_CONTEXT寄存器的虚拟地址放入g1中

747 661:    stxa            %g2, [%g1] ASI_DMMU
748         .section        .sun4v_1insn_patch, "ax"
749         .word           661b
750         stxa            %g2, [%g1] ASI_MMU
751         .previous
752 
753         membar  #Sync

我们在前面的分析中看到了很多打patch的代码,这里就是其中的一个patch,在处理器执行到这里之前,patch已经打好了,所以这里真正执行的是750行的指令。
把g2寄存器的值存入g1寄存器所标志的地址中,这里实际上是把sparc64_kern_pri_context的值存入PRIMARY_CONTEXT中,contex_id是T2用来标志不同的虚拟地址空间的,当进程访问虚拟地址时,MMU就是用contex_id来匹配该虚拟地址属于哪一个进程从而进行地址转换。而在内核执行过程中进行虚拟地址到物理地址的转换时,使用PRIMARY_CONTEXT寄存器里面的值即contex_id=0来匹配TLB表项的tag字段进行地址转换。实际上在虚拟处理器的MMU还存在这另外一个寄存器叫做NUCLUS_CONTEXT,当trap_level>0时MMU使用该寄存器里面的值进行地址转换,该寄存器里面的值强制为0。在sparc的设计里面内核和trap_level>0的核心上下文(比如中断上下文)可以位于不同的虚拟地址空间,即可以具有不同的contex_id,从而两者具有相对独立的虚拟地址空间。但是在Linux里面,显然内核和核心上下文位于同一虚拟地址空间里的,因此通过把内核执行时的context_id置为0从而时内核与核心上下文处于同一虚拟地址空间。

753         membar  #Sync
754 
755         BRANCH_IF_SUN4V(o2, 1f)

第753行,进行指令流控制,保证前面的访存指令执行完。
第755行,该宏在head_64.S中分析过,跳到1处执行。

768 1:
769         sethi   %hi(0x80000000), %o2
770         sllx    %o2, 32, %o2
771         wr      %o2, %asr25

第769,770行, 0x80000000左移32位放到%o2中
第771行,把%o2的值写入到asr25寄存器中
这一段的作用实际上是把asr25寄存器的第64位置1,asr25寄存器是System Tick Compare寄存器,它的第64位置1,则禁止时钟中断。

774         wrpr    %g0, %g0, %wstate
775 
776          call    init_irqwork_curcpu
777          nop
778 
779         /* Now we can restore interrupt state. */
780         wrpr    %l0, 0, %pstate
781         wrpr    %l1, 0x0, %pil
782 
783         ret
784         restore

第774行,清零wstate寄存器
第776行,调用init_irqwork_curcpu,它实际上是把当前虚拟处理器的trap_block变量的irq_worklist_pa变量初始化为0,这个变量在中断处理中会使用,这里把它初始化为0,可参考中断处理的相关章节。
第779到781行,重新运行中断
第783,784行,退出

到这里这段汇编就分析完了,这段汇编除了做一些初始化设置外,最终要的是把当前虚拟处理器privilged mode的trap talbe从OBP切换到Linux中来。
我们回到paging_init函数中来,
第1793行,__flush_tlb_all函数就不一行行的分析了,它实际上是调用hypervisor服务刷新TLB缓存,它的刷新规则是如果TLB缓存的地址映射表项没有被锁存到TLB中,则取消该映射,执行了这个函数之后实际上OBP所做的虚拟地址映射全都取消了,只存在Linux的地址映射
第1796行,这个函数也是调用hypervisor服务,就不一行行分析了,它实际上就是把前面提到TSB通过struct hv_tsb_descr几个注册到hypervisor,hypervisor会设置虚拟处理器上的TSB,设置之后,当产生TLB miss后,就可以先到TSB中查询相应地址转换表项了。
到这里内核页表的初始化就完成了,它做了如下工作:
把内核映像的地址映射关系锁存到TLB中,准备了并设置了TSB,准备并初始化了页全局目录和页次级目录。
在这个过程中实际上真正虚拟地址到物理地址的映射关系的只有内核映像,内核其他部分都没有建立地址映射关系,他们是在内核执行的过程中动态建立的。因为这里已经初始化了Linux trap table,它访问到相应的虚拟地址时,如果TLB中没有表项,则会发生TLB miss ,查询TSB,TSB中没有会查询页全局目录和次级目录,如果也没有则会产生页故障,为该虚拟地址填充页表项建立地址映射关系,然后会依次缓存到TSB和TLB中。这样在内核执行的过程中,整个内核空间的地址映射关系都会建立起来。
为内核初始化页表是paging_init函数最主要的工作,这里就已经完成了
第1798行,建立设备节点树,机器启动后hypervisor会扫描机器上的所有硬件设备,并把相关硬件的资源信息使用一种叫做machine description机制保存起来,在OBP中,会根据machine description把为所有的硬件设备建立一种设备节点树,在节点树里面保存了资源信息,这里是通过根据OBP的节点树在Linux中建立一个资源节点树,保存硬件资源信息。Linux设备节点树的初始化过程我们在Linux T2 设备扫描的章节中会专门介绍,这里就不提了。
第1799和1801行,这两个函数在T2平台上没有做任何操作
第1806行,sun4v_mdesc_init函数是在linux中建立上文刚刚提到的hypervisor用来描述设备资源的machine description,刚在内核中已经基于OBP节点树在Linux建立设备节点树用来描述设备资源信息了,为什么这里还要把hypervisor描述的资源信息在Linux中重现呢,这时因为OBP设备节点树中虽然大部分信息是从machine description中得到的,但还是与machine description不同,它除了machine description的基础上添加一些OBP需要传递给内核的信息比如内核参数外,它并没有把machine description所有的信息都包括进来,因此内核需要通过machine description得到一些从设备节点树中不能得到的资源信息,不过内核主要还是使用设备节点树获得资源信息的。

machine description实现:
Linux中实现访问hypervisor的machine description的机制的代码主要在arch/sparc/kernel/mdesc.c文件中。
我们先描述一下machine description,然后再讲解Linux如何使用它。
machine description包括4个部分,他们在内存中是连续存放的,如下图所示:

      header
  node block

  name block

  data block


			   图1

header中每个每个filed的大小及描述如下表所示:

   Byte offset 	       Size in bytes	     Field name 	     Description
       0	             4	transport_version 	Transport version number
       4	             4	node_blk_sz 	Size in bytes of node block
       8	             4	name_blk_sz 	Size in bytes of name block
      12	             4	data_blk_sz 	Size in bytes of data block
				        表1

描述machine description header的数据结构定义如下:

struct mdesc_hdr {

         u32     version; /* Transport version */
         u32     node_sz; /* node block size */
         u32     name_sz; /* name block size */
         u32     data_sz; /* data block size */
 } __attribute__((aligned(16)));

name block紧跟这node block,它里面存放了许多的字符串,这些字符串都是以字符串结束符(‘\0’)结束,每个字符串都是node block中某个node或element的名称。
node block紧跟header,里面每个element占有16个字节,因此node block可以看作是每个元素占有16字节空间的数组,node block中每个element可以由它在该数组中的下标唯一标志。node block中每个element的16字节的各个filed描述如下表所示:

Byte offset 	Size in bytes 	Field name 	Description
0		1		tag 								类型
1		1		name_len 					名称的长度(name size),它的名称位于name block中
2		2		_reserved_field 		保留
4		4		name_offset 				名称字符串在name block中的位置
8		8		val  								类型为“NODE”,“PROP_VAL”或“PROP_ARC”时的64位值
8		4		data_len 					类型为“PROP_STR”或“PROP_DATA”时该成员在data block中数据的长度
12	4		data_offset 				类型为“PROP_STR”或“PROP_DATA”该成员数据在data block中的位置

描述node block element的数据结构定义如下:

struct mdesc_elem {
  	u8      tag;
 #define MD_LIST_END     0x00
 #define MD_NODE         0x4e
 #define MD_NODE_END     0x45
 #define MD_NOOP         0x20
 #define MD_PROP_ARC     0x61
 #define MD_PROP_VAL     0x76
 #define MD_PROP_STR     0x73
 #define MD_PROP_DATA    0x64
         u8      name_len;
          u16     resv;
          u32     name_offset;
          union {
                 struct {
                         u32     data_len;
                         u32     data_offset;
                  } data;
                  u64     val;
          } d;
 };

data block里面则存放有node block element相关联的信息了,可以是硬件资源信息,也可以是其他的,这些数据具体有什么含义有node block element类型来定义。

machine description描述硬件资源使用情况是以NODE(节点)为基本单位的,一个NODE描述某一个硬件设备或虚拟设备所有资源信息,这些资源可能包括很多种,比如某一个外围设备可能使用既使用内存空间又使用I/O空间,内存空间资源和I/O空间资源就是两种相互独立的资源,由比如外围设备可能是PCI设备,也可能是USB设备,那么设备类型也可以看作一种资源,设备的资源可以使用property(属性)来描述,某一个属性描述了某一种资源的使用情况。因此一个NODE可以包含多个property,其中的每个property都描述了NODE的某一种资源的使用情况。
一个NODE在node block中是由多个连续相邻的node block elements组成的,其中node block中的NODE element(类型为NODE的element,见表3)标志了一个NODE的开始,
NODE_END element标志了一个NODE的结束,在NODE element和NODE_END element之间的element可以没有或者有多个elements,如果没有的话就说明该NODE没有property,如果有一个或多个elements的话,它们只能是PROP_VAL,PROP_STR,PROP_ARC或者PROP_DATA类型的element,这几种类型的element都标志了该NODE的一个property,其中如果该element类型为PROP_VAL,则该property描述的资源信息就存放在该element 16个字节中的val field中,而其他3中类型的element,其property描述的资源信息保存在data block中,由element 16个字节中的data_len 和data_offset filed指出该信息在data block中的位置和大小。所以data block中存放的就是所有NODE的这三种类型的property的资源信息。
node block中所有的elements都是有名称的,它们的名称都位于name block中,有element 的name_len 和name_offset field指出名称在name block的大小和位置。
一个NODE由在node block中由多个连续的相邻elements组成,其中标志NODE开始NODE类型的element的名称即是该NODE的名称,一个NODE包含多 个property,每个property的名称就是描述该peoperty的element的名称。
每个NODE都有一个唯一标志它的ID,它的ID其实就是该NODE在node block中的位置。
在node block可能存在这多个NODE,为了快速的找到每个NODE,每个标志NODE开始的NODE类型的elment的 value filed存放了下一个NODE在的node block中的位置,即下一个NODE的NODE ID,这样所有的NODE就组成了一个链表,链表的第一个元素就是nodeblock中的第一个NODE,叫做root NODE。
这些所有的NODE同时通过它们的名称组成了一个树形结构,用以描述不同NODE之间的父子关系,比如名称为”/cpu”的节点就是跟root 节点的子节点 /pci@0/pci@1的节点就是/pci@0节点的子节点。
machine description机制的讲解就到这里,下面看看Linux是怎么支持它的。
在machine description讲解中已经看到了Linux中为描述machine description的header block和node block element所定义的结构体,但这两个结构体并非Linux支持machine description的总的管理结构,总的管理结构定义如下:

 struct mdesc_handle {
          struct list_head        list;
          struct mdesc_mem_ops    *mops;
          void                    *self_base;
          atomic_t                refcnt;
          unsigned int            handle_size;
          struct mdesc_hdr        mdesc;
 };

该结构的各个字段有什么含义,我们到用到的时候再说。
现在我们回过头来看sun4v_mdesc_init函数,它定义如下:

926 void __init sun4v_mdesc_init(void)
927 {
928         struct mdesc_handle *hp;
929         unsigned long len, real_len, status;
930 
931         (void) sun4v_mach_desc(0UL, 0UL, &len);
932 
933         printk("MDESC: Size is %lu bytes.\n", len);
934 
935         hp = mdesc_alloc(len, &lmb_mdesc_ops);
936         if (hp == NULL) {
937                 prom_printf("MDESC: alloc of %lu bytes failed.\n", len);
938                 prom_halt();
939         }
940 
941         status = sun4v_mach_desc(__pa(&hp->mdesc), len, &real_len);
942         if (status != HV_EOK || real_len > len) {
943                 prom_printf("sun4v_mach_desc fails, err(%lu), "
944                             "len(%lu), real_len(%lu)\n",
945                             status, len, real_len);
946                 mdesc_free(hp);
947                 prom_halt();
948         }
949 
950         cur_mdesc = hp;
951 
952         report_platform_properties();
953 }

第931行,调用hyervisor服务得到machine description所占的内存空间的大小。
第935行,调用mdesc_alloc 函数struct mdesc_handle结构分配空间。 mdesc_alloc函数定义如下:

175 static struct mdesc_handle *mdesc_alloc(unsigned int mdesc_size,
176                                         struct mdesc_mem_ops *mops)
177 {
178         struct mdesc_handle *hp = mops->alloc(mdesc_size);
179 
180         if (hp)
181                 hp->mops = mops;
182 
183         return hp;
184 }

可以看出该函数主要调用传入第二个参数分配空间的,并把该参数保存在 struct mdesc_handle结构变量的mops成员中,由此可见该成员是为machine description分配空间的,
该函数的第二个参数是kmalloc_mdesc_memops,它定义如下:

static struct mdesc_mem_ops kmalloc_mdesc_memops = {
        .alloc = mdesc_kmalloc,
        .free  = mdesc_kfree,
};

所以分配空间的函数主要是 mdesc_kmalloc:

137 static struct mdesc_handle *mdesc_kmalloc(unsigned int mdesc_size)
138 {
139         unsigned int handle_size;
140         void *base;
141 
142         handle_size = (sizeof(struct mdesc_handle) -
143                        sizeof(struct mdesc_hdr) +
144                        mdesc_size);
145 
146         base = kmalloc(handle_size + 15, GFP_KERNEL | __GFP_NOFAIL);
147         if (base) {
148                 struct mdesc_handle *hp;
149                 unsigned long addr;
150 
151                 addr = (unsigned long)base;
152                 addr = (addr + 15UL) & ~15UL;
153                 hp = (struct mdesc_handle *) addr;
154 
155                 mdesc_handle_init(hp, handle_size, base);
156                 return hp;
157         }
158 
159         return NULL;
160 }

由于前面已经建立了一个简单的内存管理机制lmb,这里就使用lmb分配内存空间,可以看出它不光为struct mdesc_handle结构分配了空间,还为machine description分配了空间,这里,并调用 mdesc_handle_init函数初始化struct mdesc_handle结构,注意分配空间是之所以加上15,是为了保证分配的空间是16字节对齐的。mdesc_handle_init初始化包括把分配的空间清零,把分配的空间的16字节对齐的地址保存到struct mdesc_handle结构的self_base字段中,并把空间大小保存在handle_size字段中。
第941行,sun4v_mach_desc函数是调用hypervisor服务为刚刚分配的空间填充machine description,这样这个空间中就保存了hypervisror扫描的资源信息了。
第950行,把刚刚实现的Linux machine description保存到变量cur_mdesc中,以后就同过cur_mdesc变量使用它了。
第952行,通过得到的资源信息,打印一下系统信息。
这样Linux的machine description就初始化好了,那么我们怎么使用它呢,我们使用machine description最重要的目的就是找到我们所关心的NODE,以及NODE的property,因为他们描述的资源信息才是我们真正想要的东西。因此Linux里面提供了解析machine description硬件资源信息的函数,这些函数我们不会全部分析,只分析其中最常用的两个,其他的读者可自行分析。
这两个函数分别是通过NODE的名称找到该NODE,已经通过property名称得到资源信息。

392 u64 mdesc_node_by_name(struct mdesc_handle *hp,
393                        u64 from_node, const char *name)
394 {
395         struct mdesc_elem *ep = node_block(&hp->mdesc);
396         const char *names = name_block(&hp->mdesc);
397         u64 last_node = hp->mdesc.node_sz / 16;
398         u64 ret;
399 
400         if (from_node == MDESC_NODE_NULL) {
401                 ret = from_node = 0;
402         } else if (from_node >= last_node) {
403                 return MDESC_NODE_NULL;
404         } else {
405                 ret = ep[from_node].d.val;
406         }
407 
408         while (ret < last_node) {
409                 if (ep[ret].tag != MD_NODE)
410                         return MDESC_NODE_NULL;
411                 if (!strcmp(names + ep[ret].name_offset, name))
412                         break;
413                 ret = ep[ret].d.val;
414         }
415         if (ret >= last_node)
416                 ret = MDESC_NODE_NULL;
417         return ret;
418 }
419 EXPORT_SYMBOL(mdesc_node_by_name);

该函数的第二个参数from_node是一个NODE的ID,它的作用是通过它可以查找它后面的NODE,一般来说一个NODE的子NODE一定位于它的后面,这样就不用遍历整个NODE链表了,可以提高查找效率,当然如果该参数为空,则要查找整个链表。
第395行,node_block函数很简单就不分析了,它实际上计算得到node block的基地址,也就是elements数组的地址。
第396行,这个函数很简单,就就不分析了,它的作用是得到name block的基地址,放到names变量中。
第397行,得到node block中的elements的数量。
第401行,如果第一个from_node为空,则从第0个NODE开始查找。
第405行,得到下一个NODE的位置。
第408到414行的while循环则就是查找过程了,它会使用name block中该节点的名称匹配传入的名称,如果相等,则找到了该节点,把NODE ID 返回。

通过NODE得到其名称为name的property的函数为mdesc_get_property:

421 const void *mdesc_get_property(struct mdesc_handle *hp, u64 node,
422                                const char *name, int *lenp)
423 {
424         const char *names = name_block(&hp->mdesc);
425         u64 last_node = hp->mdesc.node_sz / 16;
426         void *data = data_block(&hp->mdesc);
427         struct mdesc_elem *ep;
428 
429         if (node == MDESC_NODE_NULL || node >= last_node)
430                 return NULL;
431 
432         ep = node_block(&hp->mdesc) + node;
433         ep++;
434         for (; ep->tag != MD_NODE_END; ep++) {
435                 void *val = NULL;
436                 int len = 0;
437 
438                 switch (ep->tag) {
439                 case MD_PROP_VAL:
440                         val = &ep->d.val;
441                         len = 8;
442                         break;
443 
444                 case MD_PROP_STR:
445                 case MD_PROP_DATA:
446                         val = data + ep->d.data.data_offset;
447                         len = ep->d.data.data_len;
448                         break;
449 
450                 default:
451                         break;
452                 }
453                 if (!val)
454                         continue;
455 
456                 if (!strcmp(names + ep->name_offset, name)) {
457                         if (lenp)
458                                 *lenp = len;
459                         return val;
460                 }
461         }
462 
463         return NULL;
464 }

第424行,得到name block的地址
第425行,得到node block中elements的数量。
第426行,得到data blockl的地址
第432行,根据得到的NODE Id从node block中得到其NODE elements(第一个element)。
第434的奥461行的循环遍历给NODE 的所有property,匹配其在name block中的名称,如果匹配,则如果property为PROP_VAL,则返回其element中val field,并把数据长度8存入最后一个参数中,如果是其他类型的property,则从data block中找到器信息位置返回并把数据长度存入最后一个参数中。

到这里machine description就讲完了,我们接着回到paging_init函数中来,
第1806行,cpu_all_mask是一个CPU位图,它应该标志了系统中所有的CPU的ID
,我们看这个函数的实现:

807 void __cpuinit mdesc_populate_present_mask(cpumask_t *mask)
808 {
809         if (tlb_type != hypervisor)
810                 return;
811 
812         ncpus_probed = 0;
813         mdesc_iterate_over_cpus(record_one_cpu, NULL, mask);
814 }

第812行,因为系统之前从未初始个过CPU位图,因此这里先把探测到的CPU的数量初始化为0;
第831行,调用mdesc_iterate_over_cpus函数:

768 static void * __cpuinit mdesc_iterate_over_cpus(void *(*func)(struct mdesc_handle *, 	u64, int, void *), void *arg, cpumask_t *mask)
769 {
770         struct mdesc_handle *hp = mdesc_grab();
771         void *ret = NULL;
772         u64 mp;
773 
774         mdesc_for_each_node_by_name(hp, mp, "cpu") {
775                 const u64 *id = mdesc_get_property(hp, mp, "id", NULL);
776                 int cpuid = *id;
777 
778 #ifdef CONFIG_SMP
779                 if (cpuid >= NR_CPUS) {
780                         printk(KERN_WARNING "Ignoring CPU %d which is "
781                                ">= NR_CPUS (%d)\n",
782                                cpuid, NR_CPUS);
783                         continue;
784                 }
785                 if (!cpu_isset(cpuid, *mask))
786                         continue;
787 #endif
788 
789                 ret = func(hp, mp, cpuid, arg);
790                 if (ret)
791                         goto out;
792         }
793 out:
794         mdesc_release(hp);
795         return ret;
796 }

第770行, mdesc_grab函数很简单,就不分析了。它就是通过cur_mdesc变量得到Linux中当前的machine description,有此可见我们是要利用machine description得到cpu系统的CPU相关信息。
第774行,mdesc_for_each_node_by_name函数也不分析了,看名字就知道他是变量node block中的所有NODE,找到其中所有名称包含”cpu”的NODE,从而形成一个循环。
第775到792行就是循环体的内容,它会得到cpu NODE 的“id” property,然后用该cpuid设置cpu_all_mask位图,其中第789行,执行的是函数record_one_cpu,这个函数也不分析了,它其实是把探测到的cpu的数量加一,然后设置cpu_present_mask位图。
这样,当 mdesc_populate_present_mask执行完后,就设置了两个cpu位图:
cpu_all_mask和cpu_present_mask,并且变量ncpus_probed就存放了系统中CPU的数量。

我们回到paging_init函数中来,
第1808行,mdesc_fill_in_cpu_data我们这里不执行,因为我们是SMP架构的,这里不执行的原因是这个函数要操作per_cpu域,而这里per_cpu域现在并没有初始化,因此在per_cpu域初始化后,这个函数到那时会被执行,可以参考Linux T2 SMP支持实现的章节。
第1816到1820行的循环为每个cpu建立了两个栈,这个栈分别软件中断和普通中断执行时使用的栈,他们保存在两个数组softirq_stack和hardirq_stack中。

Bootmem 内存分配器:
第1823行,前面的内存分配都是使用之前建立的简单临时的内存管理机制lmb,这里就要初始化另外一个内存管理机制:bootmem内存分配器。
bootmem与lmb一样也是一个临时的内存管理机器,与lmb不同的时,它对内存的管理是基于页框的,它会为所有物理页的使用情况建立一个位图。bootmem的主要目的是为Linux真正的内存管理机制做准备,在之后的真正的内存管理机制初始化时会在bootmem的基础上建立真正的内存管理机制。
bootmem的管理结构叫做bootmem_data_t,它定义如下:

 typedef struct bootmem_data {
          unsigned long node_min_pfn;
          unsigned long node_low_pfn;
          void *node_bootmem_map;
          unsigned long last_end_off;
          unsigned long hint_idx;
          struct list_head list;
 } bootmem_data_t;

我们知道计算机内存的组织结构分为两种,一种叫做NUMA结构,一种叫做UMA结构,我们并不准备讨论NUMA和UMA的特征和区别,只需要知道在NUMA结构下的CPU根据其内存及I/O资源分为了多个节点,那么每个节点都会有一个bootmem的管理结构bootmem_data_t,而在UMA结构下整个物理内存被看作一个节点,也就只存在一个bootmem管理结构了。因此针对NUMA和UMA,botmem的初始化是不同的,我们这里只关注UMA结构。
我们现在看看 bootmem_data_t中各成员的含义:
node_min_pfn:系统中第一个物理页的页帧号
node_low_pfn:该成员却不是系统中最后一个物理页的页帧号,而是ZONE_NOMAL区域中最后一个物理页的页帧号,在Linux中,每个内存节点都可以分为3个内存区域,分别是ZONE_DMA,ZONE_NOMAL已经ZONE_HIGH,其中在32位体系结构中ZONE_DMA和ZONE_NOMAL则覆盖了0到896M的物理地址范围,ZONE_HIGH就是高端内存了,他们是不被bootmem所管理的。不过我们这里是64位体系结构,内存空间很大,所以我们的内存应该都会位于ZONE_DMA和ZONE_NOMAL的区域内。
node_bootmem_map:该成员就是内存页框位图的地址
last_end_off:最近一次内存分配的分配地址在最后一个分配的页中的偏移
hint_idx:最近一次内存分配的页帧号
我们看bootmem_init函数:

1381 static unsigned long __init bootmem_init(unsigned long phys_base)
1382 {
1383         unsigned long end_pfn;
1384         int nid;
1385 
1386         end_pfn = lmb_end_of_DRAM() >> PAGE_SHIFT;
1387         max_pfn = max_low_pfn = end_pfn;
1388         min_low_pfn = (phys_base >> PAGE_SHIFT);
1389 
1390         if (bootmem_init_numa() < 0)
1391                 bootmem_init_nonnuma();
1392 
1393         /* XXX cpu notifier XXX */
1394 
1395         for_each_online_node(nid)
1396                 bootmem_init_one_node(nid);
1397 
1398         sparse_init();
1399 
1400         return end_pfn;
1401 }

第1386行,通过lmb得到系统中最后一个页框的页帧号放到end_pfn中
第1387行,max_pfn,pax_low_pfn都存着最后一个页框的页帧号
第1388行,系统中第一个物理页的页帧号
我们是uma结构,接下来就执行第1391行,我们看 bootmem_init_nonnuma函数:

1279 static void __init bootmem_init_nonnuma(void)
1280 {
1281         unsigned long top_of_ram = lmb_end_of_DRAM();
1282         unsigned long total_ram = lmb_phys_mem_size();
1283         unsigned int i;
1284 
1285         numadbg("bootmem_init_nonnuma()\n");
1286 
1287         printk(KERN_INFO "Top of RAM: 0x%lx, Total RAM: 0x%lx\n",
1288                top_of_ram, total_ram);
1289         printk(KERN_INFO "Memory hole size: %ldMB\n",
1290                (top_of_ram - total_ram) >> 20);
1291 
1292         init_node_masks_nonnuma();
1293 
1294         for (i = 0; i < lmb.memory.cnt; i++) {
1295                 unsigned long size = lmb_size_bytes(&lmb.memory, i);
1296                 unsigned long start_pfn, end_pfn;
1297 
1298                 if (!size)
1299                         continue;
1300 
1301                 start_pfn = lmb.memory.region[i].base >> PAGE_SHIFT;
1302                 end_pfn = start_pfn + lmb_size_pages(&lmb.memory, i);
1303                 add_active_range(0, start_pfn, end_pfn);
1304         }
1305 
1306         allocate_node_data(0);
1307 
1308         node_set_online(0);
1309 }

第1291行,这个函数就不分析了,它初始化每个cpu内存节点的数组和位图,由于是uma结构都只有一个节点,所以所有cpu都存的是0号节点
第1294到1394行,是一个循环,这个循环里面的函数由于都是Linux里面的标准函数不是我们这里实现的体系结构相关的函数,就不分析了。它从lmb中解析处所有的可用的物理页的页帧号,放在在一个early_node_map的数组中
第1306行,函数allocate_node_data:

 818 static void __init allocate_node_data(int nid)
 819 {
 820         unsigned long paddr, num_pages, start_pfn, end_pfn;
 821         struct pglist_data *p;
 822 
 835 
 836         p = NODE_DATA(nid);
 837 
 838         get_pfn_range_for_nid(nid, &start_pfn, &end_pfn);
 839         p->node_start_pfn = start_pfn;
 840         p->node_spanned_pages = end_pfn - start_pfn;
 841 
 842         if (p->node_spanned_pages) {
 843                 num_pages = bootmem_bootmap_pages(p->node_spanned_pages);
 844 
 845                 paddr = lmb_alloc_nid(num_pages << PAGE_SHIFT, PAGE_SIZE, nid,
 846                                       nid_range);
 847                 if (!paddr) {
 848                         prom_printf("Cannot allocate bootmap for nid[%d]\n",
 849                                   nid);
 850                         prom_halt();
 851                 }
 852                 node_masks[nid].bootmem_paddr = paddr;
 853         }

第1306行,通过宏NODE_DATA得到 struct pglist_data结构,这个结构的细节就不讲另外,只要知道它的成员bdata成员就指向我们前面所说的bootmem的管理结构就行了。
第838行,get_pfn_range_for_nid函数实际上是从刚刚提到的early_node_map数组中得到系统中最大页帧号和最小页帧号分别赋值到struct pglist_data的两个成员中
第843行,bootmem_bootmap_pages是bootmem的标准函数,它计算出需要多少个页来存储当前系统中页框的位图
第845行,利用lmb分配位图
第852行,位图地址存到一个全局变量中

回到bootmem_init_nonnuma函数
第1308行,该函数注册一个节点

回到bootmem_init函数中来,刚刚分配了一个节点号为0的内存节点,
第1396行,初始化这个节点:

1349 static void __init bootmem_init_one_node(int nid)
1350 {
1351         struct pglist_data *p;
1352 
1353         numadbg("bootmem_init_one_node(%d)\n", nid);
1354 
1355         p = NODE_DATA(nid);
1356 
1357         if (p->node_spanned_pages) {
1358                 unsigned long paddr = node_masks[nid].bootmem_paddr;
1359                 unsigned long end_pfn;
1360 
1361                 end_pfn = p->node_start_pfn + p->node_spanned_pages;
1362 
1363                 numadbg("  init_bootmem_node(%d, %lx, %lx, %lx)\n",
1364                         nid, paddr >> PAGE_SHIFT, p->node_start_pfn, end_pfn);
1365 
1366                 init_bootmem_node(p, paddr >> PAGE_SHIFT,
1367                                   p->node_start_pfn, end_pfn);
1368 
1369                 numadbg("  free_bootmem_with_active_regions(%d, %lx)\n",
1370                         nid, end_pfn);
371                 free_bootmem_with_active_regions(nid, end_pfn);
1372 
1373                 trim_reserved_in_node(nid);
1374 
1375                 numadbg("  sparse_memory_present_with_active_regions(%d)\n",
1376                         nid);
1377                 sparse_memory_present_with_active_regions(nid);
1378         }

第1366行,这仍然是一个标准函数,它初始化bootmem的管理结构,包括我们刚刚分配的位图存放在它的 node_bootmem_map成员中,并把位图的所有为初始化为1。
第1371行,为early_node_map数组中记录的当前系统可用内存在页框位图中设置其代表位。
第1373行,trim_reserved_in_node,把lmb中保留的内存在页框位图中设置其代表位
第1377行,这一行做什么的我们这里就不用管了,它好像是把位图中为0的也框设置为active状态
到这里我们的位图就建立起来了,bootmem管理结构也同样初始化完了,该结构保存在 struct pglist_data结构的btext成员中。
我们的bootmem的初始化就分析到这里,回到paging_init函数中来。

第1826行,初始化max_mapnr变量为最后一个页帧号。
第1828行,这个函数在我们这里什么也不做。
第1831到1838行的语句就不细分析了,他们其实非常复杂,已经涉及到初始化Linux真正的内存管理机制了,它会根据系统中物理页资源初始化所有的页框结构和内存区域结构。

paging_init函数就分析完了。

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多元地址/数据复用总线。
该错误提示表明系统在执行构建脚本时无法找到名为 `sparc-gaisler-elf-gcc` 的编译器。这种情况通常发生在目标平台为 SPARC 架构(例如 Gaisler LEON 处理器)的嵌入式开发中,使用了特定的交叉编译工具链。以下是一些可能的原因及对应的解决方案: ### 1. **交叉编译工具链未安装** - `sparc-gaisler-elf-gcc` 是用于 Gaisler SPARC 处理器的交叉编译器,常见于 RTEMS 或其他嵌入式操作系统开发中。 - 需要确认是否已经正确安装适用于 Gaisler SPARC 架构的交叉编译工具链。如果尚未安装,可以从官方资源获取并安装[^1]。 ### 2. **环境变量 PATH 未配置** - 如果已安装交叉编译工具链但仍然报错,可能是由于其可执行文件路径未添加到系统的 `PATH` 环境变量中。 - 检查工具链的安装目录,例如 `/opt/gaisler/bin/`,并将该路径添加到 `PATH` 中: ```bash export PATH=/opt/gaisler/bin:$PATH ``` - 可将上述命令添加到 `~/.bashrc` 或 `~/.zshrc` 文件中以确保每次启动终端时自动生效。 ### 3. **构建脚本依赖的工具链名称不匹配** - 构建脚本可能期望调用 `sparc-gaisler-elf-gcc`,但实际上安装的工具链可能使用不同的命名方式,例如 `sparc-rtems5-gcc` 或 `sparc-elf-gcc`。 - 可通过运行 `which sparc-*gcc` 查找当前系统中存在的 SPARC 编译器,并检查 `build.sh` 脚本中的编译器名称是否与实际可用的工具一致。如有必要,修改脚本中使用的编译器名称。 ### 4. **使用脚本设置工具链路径** - 在某些开发环境中,例如 RTEMS,需要运行初始化脚本来设置正确的工具链路径。例如: ```bash source /opt/rtems-6/etc/bash_completion.d/rtems-env.sh ``` - 该脚本会自动配置必要的环境变量,包括 `PATH` 和 `CC`,确保构建过程使用正确的交叉编译器。 ### 5. **手动指定编译器路径** - 如果不确定环境变量是否有效,可以在构建脚本或 Makefile 中显式指定 `sparc-gaisler-elf-gcc` 的完整路径,例如: ```makefile CC = /opt/gaisler/bin/sparc-gaisler-elf-gcc ``` ### 6. **验证工具链安装** - 安装完成后,运行以下命令验证工具链是否可用: ```bash sparc-gaisler-elf-gcc --version ``` - 如果输出显示版本信息,则表示工具链已正确安装并配置。 ### 7. **使用包管理器安装工具链(如适用)** - 对于某些 Linux 发行版,可以通过包管理器安装 Gaisler 工具链。例如,在 Fedora 上可以尝试: ```bash sudo dnf install sparc-gaisler-elf-gcc ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值