这样xen内核就开始启动了.....
Xen Hypervisor运行在Ring0,在启动过程中,Xen首先被引导:系统由Grub启动,遵循Multiboot引导规范;然后Linux内核做为module也被引导入内存,同时initrd镜像文件也一样。整个引导过程如下图示:
1 当加电后首先是BIOS自检、Grub引导。Xen遵循Multiboot引导规范,它需要从Grub读入内存信息,通过置位标志位第1位来实现的。
主要过程是从grub-xen(head.S, trampoline.S, x86_32.S,位于\xen\arch\x86\boot中:),
head.S:装入GDT(trampoline_gdt); 进入保护模式;初始化页表,将线性空间的0-12M和__PAGE_OFFSET-__PAGE_OFFSET+12M都映射到物理地址的0-12M;而将线性空间的12M-16M映射到物理地址的12M-16M;解析早期命令行参数;跳转到trampoline_boot_cpu_entry;
trampoline.S:进入实模式,读取内存,磁盘,显示信息;再次进入保护模式,装入新的GDT(gdt_table);加载前面初始化了的页表,启用分页机制,跳转到__high_start。然后就是x86_32.S文件。
2.x86_32.S是从Grub进入Xen的入口文件。Grub根据镜像头信息获得入口地址,然后读入整个镜像,最后把控制权交给Xen。
可以使用readelf命令来查看编译后生成的Xen的内核,它表明文件类型为可执行文件,并且程序的入口点是Oxl0000(原本这是Linux内核的位置),这表明Xen被引导程序放在了物理内存1M的位置上。
在x86_32.S的start入口点上,主要是为进行后续工作做准备,包括简单地设置GDT、IDT以及初始化分页等。然后start将保存着引导程序启动信息地址的EBX压栈,再调用真正的初始化流程函数。
3.__startxen()函数中首先会从启动信息中获取物理内存分配情况,初始化E820内存图。
物理内存是只有Xen才有权限进行管理和分配的,无论是Domain0还是DomainU,它们得到的都是Xen给它们的物理内存假相。
4.之后是分页初始化、IRQ中断初始化、调度程序初始化、异常处理程序表初始化、时间设置、安全机制设置等初始化工作,并且Xen会初始化一个空闲虚拟域(Idle Domain),当没有合适的虚拟域可以运行的时候,Xen会选择空闲虚拟域来运行,这很类似于Linux中的init进程。
5.当Xen初始化工作结束后,便开始设置Domain0的数据结构。
每个虚拟域都有一个struct domain结构体,在这里主要定义了它的ID、共享信息、内存分配、虚拟CPU、事件通道和授权表等。分配好DomainO的数据结构,Xen将通过construct_dom0()函数来将主控域的运行环境设置好。
6。然后Xen将控制权交给Domain0中的Linux,而自己进入idie_loop。主控域得到控制权后,开始自己的引导过程,只是它需要的信息是从xen_start_info数据结构中获取。进入Domain0的EIP指向_start_32,它会再跳转到start_kernel,之后的启动流程就和Linux的正常启动流程基本一样了。
启动汇编部分代码是xen 的引导启动程序,位于./xen/arch/x86/boot目录下。代码描述了从xen加载到调用第一个C函数“__start_xen”之间的初始化系统环境过程。主要涉及的文件流程为head.S->trampoline.S->x86_32.s,其中head.s为冲GRUB进入XEN的入口文件,首先看看head.s部分都做了什么(只看32位体系)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
|
/* 只能由 grub 来引导,head.S 是从GRUB进入XEN 的入口文件;
开始执行的第一个汇编文件,包括初始化页表,解析早期命令行参数等工作
*/
#include
#include
#include
#include
#include
#include
#include
.text
.code32
/*当xen运行时,cpu已经处于保护模式了,和LINUX内核的处理方式一致,虚拟地址等于物理地址加上固定值*/
/*在xen\include\asm-x86\x86_32\page.h中有__XEN_VIRT_START的定义*/
#define sym_phys(sym) ((sym) - __XEN_VIRT_START)
/**
*xen 编译时的映像布局由xen\arch\x86\xen.lds.S 控制:
...
#ifdef __x86_64__
#define FORMAT "elf64-x86-64"
#else
#define FORMAT "elf32-i386"
#endif
ENTRY(start)
#endif
OUTPUT_FORMAT(FORMAT, FORMAT, FORMAT)
#ifdef __x86_64__
OUTPUT_ARCH(i386:x86-64)
#else
OUTPUT_ARCH(i386)
#endif
PHDRS
{
text PT_LOAD ;
}
SECTIONS
{
. = __XEN_VIRT_START + 0x100000;
_start = .;
.text : {
_stext = .; //Text and read-only data
*(.text)
*(.text.cold)
*(.text.unlikely)
*(.fixup)
*(.gnu.warning)
_etext = .; //End of text section
} :text = 0x9090
...
**/
/*根据INTEL手册GDT第一项无用,故而从0x08开始*/
/*ring0,code,32-bit mode*/
#define BOOT_CS32 0x0008
/*ring0,code,64-bit mode*/
#define BOOT_CS64 0x0010
/*ring0,data*/
#define BOOT_DS 0x0018
/*real-mode code*/
#define BOOT_PSEUDORM_CS 0x0020
/*5 real-mode data*/
#define BOOT_PSEUDORM_DS 0x0028
ENTRY(start)
jmp __start
.align 4
/*** MULTIBOOT HEADER ****/
#define MULTIBOOT_HEADER_FLAGS (MULTIBOOT_HEADER_MODS_ALIGNED | \
MULTIBOOT_HEADER_WANT_MEMORY)
/* Magic number indicating a Multiboot header. */
.
long
MULTIBOOT_HEADER_MAGIC
/* Flags to bootloader (see Multiboot spec). */
.
long
MULTIBOOT_HEADER_FLAGS
/* Checksum: must be the negated sum of the first two fields. */
.
long
-(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
/**
* 上面的定义是给grub看的,表明支持multiboot, 详细内容见multiboot协议
**/
.section .init.text,
"ax"
/*.asciz is just like .ascii, but each string is followed by a zero
byte.*/
.Lbad_cpu_msg: .asciz
"ERR: Not a 64-bit CPU!"
.Lbad_ldr_msg: .asciz
"ERR: Not a Multiboot bootloader!"
bad_cpu:
/*打印bad cpu错误*/
mov $(sym_phys(.Lbad_cpu_msg)),%esi # Error message
jmp print_err
not_multiboot:
/*打印非多启动错误*/
mov $(sym_phys(.Lbad_ldr_msg)),%esi # Error message
print_err:
/*这里的打印用的是最基本的往显卡缓存写入数据的方式*/
mov $0xB8000,%edi # VGA framebuffer
1: mov (%esi),%bl
test %bl,%bl # Terminate on
'\0'
sentinel
2: je 2b
mov $0x3f8+5,%dx # UART Line Status Register
3: in %dx,%al
test $0x20,%al # Test THR Empty flag
je 3b
mov $0x3f8+0,%dx # UART Transmit Holding Register
mov %bl,%al
out %al,%dx # Send a character over the serial line
movsb # Write a character to the VGA framebuffer
mov $7,%al
stosb # Write an attribute to the VGA framebuffer
jmp 1b
gdt_boot_descr:
/*GDT定义,传统模式下的全局描述符表寄存器(GDTR)长48位,由16位的界限和32位的基地址构成。由于段描述符总是8字节长,故界限的值应为8N-1。
Trampoline_gdt共定义了6个描述符项,界限是6*8-1。*/
.word 6*8-1
.
long
sym_phys(trampoline_gdt)
__start:
cld
cli
/* Initialise GDT and basic data segments. */
lgdt %cs:sym_phys(gdt_boot_descr)
mov $BOOT_DS,%ecx
mov %ecx,%ds
mov %ecx,%es
mov %ecx,%ss
/*
验证并存储多重启动信息,详见“多重启动规范”。
当boot loader引导32位操作系统的时候,机器必须有如下的状态:
EAX:
必须包含魔数0X2BADB002,这个值告诉操作系统目前它是由兼容的Multiboot 的boot loader引导的。
EBX:
必须包含boot loader提供的多重引导信息结构的32位物理地址。
CS:
必须是32位的读/执行的代码段,偏移是0以及界限是 0XFFFFFFFF。具体值没有定义。
SS:
必须是32位的读/执行数据段,偏移是0以及界限是 0XFFFFFFFF。具体值没有定义。
A20 GATE :
必须enable。
CR0:
31位(PG)必须清除,第0位(PE)必须设置。其他位没有定义。
EFLAGS:
第17(VM)位必须清除,第9位(IF)必须清除,其他位没有定义。
*/
/* Check for Multiboot bootloader */
cmp $0x2BADB002,%eax
jne not_multiboot
/* Set up trampoline segment 64k below EBDA */
movzwl 0x40e,%eax
/* EBDA segment */
cmp $0xa000,%eax
/* sanity check (high) */
jae 0f
cmp $0x4000,%eax
/* sanity check (low) */
jae 1f
0:
movzwl 0x413,%eax
/* use base memory size on failure */
shl $10-4,%eax
1:
sub $0x1000,%eax
/* From arch/x86/smpboot.c: start_eip had better be page-aligned! */
xor %al, %al
shl $4, %eax
mov %eax,sym_phys(trampoline_phys)
/* Save the Multiboot info struct (after relocation) for later use. */
mov $sym_phys(cpu0_stack)+1024,%esp
push %ebx
call reloc
mov %eax,sym_phys(multiboot_ptr)
/* Initialize BSS (no nasty surprises!) */
/*初始化BSS段,存放程序中未初始化的全局变量。
BSS段在xen\arch\x86\x86_32\xen.lds.S中定义*/
mov $sym_phys(__bss_start),%edi
mov $sym_phys(_end),%ecx
sub %edi,%ecx
xor %eax,%eax
rep stosb
/*
查询并保存CPU拓展信息。
CPUID指令可提供关于处理器的实现及其能力的完整信息,任意特权级的软件都可以使用它。
EAX寄存器用于决定CPUID生成什么信息
EAX = 0x80000000,返回信息:
EAX: Maximum Input Value for Extended Function CPUID Information. PIV之后的CPU,均大于0x80000000
EBX: Reserved
ECX: Reserved
EDX: Reserved
EAX = 0x80000001,返回信息:
EAX: Extended Processor Signature and Feature Bits.
EBX: Reserved
ECX: Bit 0: LAHF/SAHF available in 64-bit mode
Bits 31-1 Reserved
EDX: Bits 10-0: Reserved
Bit 11: SYSCALL/SYSRET available (when in 64-bit mode)
Bits 19-12: Reserved = 0
Bit 20: Execute Disable Bit available
Bits 28-21: Reserved = 0
Bit 29: Intel? 64 Architecture available if 1
Bits 31-30: Reserved = 0
cpuid_ext_features在xen\arch\x86\boot\trampoline.S中定义。boot_cpu_data在\xen\include\asm-x86 \ processor.h中定义,是cpuinfo_x86的实例。
CPUINFO86_ext_features 在xen\arch\x86\x86_32 \ asm-offsets.c中定义:OFFSET(CPUINFO86_ext_features, struct cpuinfo_x86, x86_capability[1]);
OFFSET解释如下:
#define offsetof (s, m) (size_t)&(((s*)0)->m)
m为结构体s中的一项,返回m距结构体起始地址的偏移量。ANSI C中常数0允许转换成任何类型的指针,但转换后指针为NULL。例中&(((s*)0)->m)这一步,并不访问m元素,只是获取m的地址,编译时不生成访问m的代码。
#define DEFINE(_sym,_val) __asm__ __volatile__ (“\n->” #_sym “%0” #_val:: “i”(_val))
#是注释符号;%0是占位符,这里指代“i”(_val)。
#define OFFSET(_sym, _str, _mem) DEFINE(_sym, offsetof(_str, _mem))
这条宏是将_str结构体的_mem项的偏移量赋值给_sym。
*/
/* Interrogate CPU extended features via CPUID. */
mov $0x80000000,%eax
cpuid
xor %edx,%edx
cmp $0x80000000,%eax # any function > 0x80000000?
jbe 1f
mov $0x80000001,%eax
cpuid
1: mov %edx,sym_phys(cpuid_ext_features)
mov %edx,sym_phys(boot_cpu_data)+CPUINFO86_ext_features
#if defined(__x86_64__)
/* Check for availability of long mode. */
bt $29,%edx
jnc bad_cpu
/* Initialise L2 identity-map and xen page table entries (16MB). */
mov $sym_phys(l2_identmap),%edi
mov $sym_phys(l2_xenmap),%esi
mov $sym_phys(l2_bootmap),%edx
mov $0x1e3,%eax
/* PRESENT+RW+A+D+2MB+GLOBAL */
mov $8,%ecx
1: mov %eax,(%edi)
add $8,%edi
mov %eax,(%esi)
add $8,%esi
mov %eax,(%edx)
add $8,%edx
add $(1<
loop 1b
/* Initialise L3 identity-map page directory entries. */
mov $sym_phys(l3_identmap),%edi
mov $(sym_phys(l2_identmap)+7),%eax
mov $4,%ecx
1: mov %eax,(%edi)
add $8,%edi
add $PAGE_SIZE,%eax
loop 1b
/* Initialise L3 xen-map page directory entry. */
mov $(sym_phys(l2_xenmap)+7),%eax
mov %eax,sym_phys(l3_xenmap) + l3_table_offset(XEN_VIRT_START)*8
/* Initialise L3 boot-map page directory entry. */
mov $(sym_phys(l2_bootmap)+7),%eax
mov %eax,sym_phys(l3_bootmap) + 0*8
/* Hook identity-map, xen-map, and boot-map L3 tables into PML4. */
mov $(sym_phys(l3_bootmap)+7),%eax
mov %eax,sym_phys(idle_pg_table) + 0*8
mov $(sym_phys(l3_identmap)+7),%eax
mov %eax,sym_phys(idle_pg_table) + l4_table_offset(DIRECTMAP_VIRT_START)*8
mov $(sym_phys(l3_xenmap)+7),%eax
mov %eax,sym_phys(idle_pg_table) + l4_table_offset(XEN_VIRT_START)*8
#else
/*32位下2M页面大小,开PAE方式映射,在这里我们也看出32位内核为XEN需要开启PAE,初始化页表,
将线性空间的0-12M和__PAGE_OFFSET-__PAGE_OFFSET+12M都映射到物理地址的0-12M;而将线性空间的12M-16M映射到物理地址的12M-16M(注意,这时并没有启用分页机制):
*/
/* Initialize low and high mappings of memory with 2MB pages */
mov $sym_phys(idle_pg_table_l2),%edi
mov $0xe3,%eax
/* PRESENT+RW+A+D+2MB */
1: mov %eax,__PAGE_OFFSET>>18(%edi)
/* high mapping */
stosl
/* low mapping */
add $4,%edi
add $(1<
cmp $DIRECTMAP_PHYS_END+0xe3,%eax
jne 1b
1: stosl
/* low mappings cover up to 16MB */
add $4,%edi
add $(1<
cmp $(16<<20)+0xe3,%eax
jne 1b
#endif
/* Initialize 4kB mappings of first 2MB or 4MB of memory. */
mov $sym_phys(l1_identmap),%edi
mov $0x263,%eax
/* PRESENT+RW+A+D+SMALL_PAGES */
#if defined(__x86_64__)
or $0x100,%eax
/* GLOBAL */
#endif
xor %ecx,%ecx
1: stosl
add $4,%edi
add $PAGE_SIZE,%eax
inc %ecx
/* VGA hole (0xa0000-0xc0000) should be mapped UC. */
cmp $0xa0,%ecx
jne 2f
or $0x10,%eax
/* +PCD */
2: cmp $0xc0,%ecx
jne 2f
and $~0x10,%eax
/* -PCD */
2: cmp $L1_PAGETABLE_ENTRIES,%ecx
jne 1b
sub $(PAGE_SIZE-0x63),%edi
#if defined(__x86_64__)
mov %edi,sym_phys(l2_identmap)
mov %edi,sym_phys(l2_xenmap)
mov %edi,sym_phys(l2_bootmap)
#else
mov %edi,sym_phys(idle_pg_table_l2)
mov %edi,sym_phys(idle_pg_table_l2) + (__PAGE_OFFSET>>18)
#endif
/* Apply relocations to bootstrap trampoline. */
mov sym_phys(trampoline_phys),%edx
mov $sym_phys(__trampoline_rel_start),%edi
mov %edx,sym_phys(trampoline_phys)
1:
mov (%edi),%eax
add %edx,(%edi,%eax)
add $4,%edi
cmp $sym_phys(__trampoline_rel_stop),%edi
jb 1b
/* Patch in the trampoline segment. */
shr $4,%edx
mov $sym_phys(__trampoline_seg_start),%edi
1:
mov (%edi),%eax
mov %dx,(%edi,%eax)
add $4,%edi
cmp $sym_phys(__trampoline_seg_stop),%edi
jb 1b
call cmdline_parse_early
/* Switch to low-memory stack. */
mov sym_phys(trampoline_phys),%edi
lea 0x10000(%edi),%esp
lea trampoline_boot_cpu_entry-trampoline_start(%edi),%eax
pushl $BOOT_CS32
push %eax
/* Copy bootstrap trampoline to low memory, below 1MB. */
mov $sym_phys(trampoline_start),%esi
mov $trampoline_end - trampoline_start,%ecx
rep movsb
/* Jump into the relocated trampoline. */
/*由上面的push代码段和IP后在这里执行ret相当于两个pop指令,直接跳转到trampoline.s中*/
lret
#include "cmdline.S"
reloc:
#include "reloc.S"
.align 16
.globl trampoline_start, trampoline_end
/*第二阶段初始化,实模式*/
trampoline_start:
#include "trampoline.S"
trampoline_end:
.text
/*第三阶段初始化*/
__high_start:
#ifdef __x86_64__
#include "x86_64.S"
#else
#include "x86_32.S"
#endif
|
汇编文件trampoline.s,为启动汇编程序第二阶段,主要工作为进入实模式,读取内存,磁盘,视频信息然后再次进入保护模式装入新的GDT(gdt_table),英文注释了很大部分,很容易理解。下面的代码注释中,从标号0开始运行,然后是标号1。
1 .code16 2 /* NB. bootsym() is only usable in real mode, or via BOOT_PSEUDORM_DS. */ 3 #undef bootsym 4 /*bootsym(s)定义的是s的相对位置*/ 5 #define bootsym(s) ((s)-trampoline_start) 6 7 #define bootsym_rel(sym, off, opnd...) \ 8 bootsym(sym),##opnd; \ 9 111:; \ 10 .pushsection .trampoline_rel, "a"; \ 11 .long 111b - (off) - .; \ 12 .popsection 13 14 #define bootsym_segrel(sym, off) \ 15 $0,$bootsym(sym); \ 16 111:; \ 17 .pushsection .trampoline_seg, "a"; \ 18 .long 111b - (off) - .; \ 19 .popsection 20 21 .globl trampoline_realmode_entry 22 trampoline_realmode_entry: 23 mov %cs,%ax 24 mov %ax,%ds 25 movb $0xA5,bootsym(trampoline_cpu_started) 26 cld 27 cli 28 lidt bootsym(idt_48) 29 lgdt bootsym(gdt_48) 30 mov $1,%bl # EBX != 0 indicates we are an AP 31 xor %ax, %ax 32 inc %ax 33 lmsw %ax # CR0.PE = 1 (enter protected mode) 34 ljmpl $BOOT_CS32,$bootsym_rel(trampoline_protmode_entry,6) 35 36 idt_48: .word 0, 0, 0 # base = limit = 0 37 gdt_48: .word 6*8-1 38 .long bootsym_rel(trampoline_gdt,4) 39 trampoline_gdt: 40 /* 0x0000: unused */ 41 .quad 0x0000000000000000 42 /* 0x0008: ring 0 code, 32-bit mode */ 43 .quad 0x00cf9a000000ffff 44 /* 0x0010: ring 0 code, 64-bit mode */ 45 .quad 0x00af9a000000ffff 46 /* 0x0018: ring 0 data */ 47 .quad 0x00cf92000000ffff 48 /* 0x0020: real-mode code @ BOOT_TRAMPOLINE */ 49 .long 0x0000ffff 50 .long 0x00009a00 51 /* 0x0028: real-mode data @ BOOT_TRAMPOLINE */ 52 .long 0x0000ffff 53 .long 0x00009200 54 55 .pushsection .trampoline_rel, "a" 56 .long trampoline_gdt + BOOT_PSEUDORM_CS + 2 - . 57 .long trampoline_gdt + BOOT_PSEUDORM_DS + 2 - . 58 .popsection 59 60 .globl cpuid_ext_features 61 cpuid_ext_features: 62 .long 0 63 64 .globl trampoline_xen_phys_start 65 trampoline_xen_phys_start: 66 .long 0 67 68 .globl trampoline_cpu_started 69 trampoline_cpu_started: 70 .byte 0 71 72 .code32 73 /*1: 从实模式跳转到这里运行,也就是正式进入保护模式*/ 74 trampoline_protmode_entry: 75 /* Set up a few descriptors: on entry only CS is guaranteed good. */ 76 mov $BOOT_DS,%eax 77 mov %eax,%ds 78 mov %eax,%es 79 80 /* Set up FPU. */ 81 fninit 82 83 /* Initialise CR4. */ 84 mov $X86_CR4_PAE,%ecx 85 mov %ecx,%cr4 86 87 /* Load pagetable base register. */ 88 mov $sym_phys(idle_pg_table),%eax 89 add bootsym_rel(trampoline_xen_phys_start,4,%eax)