-
Notifications
You must be signed in to change notification settings - Fork 102
/
lkmain.txt
434 lines (309 loc) · 16 KB
/
lkmain.txt
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
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
\section{lkmain}
\begin{verbatim}
thread_init_early()
thread_construct_first(t, "bootstrap");
在主cpu上创建一个反映当前运行状态的thread_t
拿到的cpu num是0
thread_t清零
THREAD_MAGIC暂时不知道什么用
设置thread名字
retcode_wait_queue的前驱后继都指向自己
从tpidr_el1中拿到boot_cpu_fake_thread_pointer_location的地址
__has_feature(safe_stack)是clang的特性,对gcc来说,
unsafe_sp就是0(在start.S里安排的)
把x18保存在current_percpu_ptr里
让tpidr_el1指向这个真正的thread_t,也就是percpu里的
idle_thread
把这个thread加到全局的thread list上
sched_init_early()初始化各个cpu的run queue为空。
调用C++的全局变量的构造函数__init_array_start。
lk_init_level()
required_flag = LK_INIT_FLAG_PRIMARY_CPU
start_level = LK_INIT_LEVEL_EARLIEST
stop_level = LK_INIT_LEVEL_ARCH_EARLY - 1
这次没有匹配的init函数可以被调用。(也许我漏掉了?)
arch_early_init();
x64:
x86_mmu_early_init();
x86_mmu_percpu_init();
设置cr0, cr4
arm64:
arch_curr_cpu_num_slow()返回0,因为这时arm64_cpu_map还没有初始化。
arch.cpp: arm64_cpu_early_init()
把x18指向当前cpu的percpu.
让VBAR_EL1指向exceptions.S中的arm64_el1_exception_base.
把一些cpu feature读到arm64_features里
打开DAIF遮掩位。
lk_primary_cpu_init_level(LK_INIT_LEVEL_ARCH_EARLY, LK_INIT_LEVEL_PLATFORM_EARLY - 1);
platform_early_init()
x64:
pc_init_debug_default_early();
platform_save_bootloader_data();
multiboot_info_t* mi = (multiboot_info_t*)X86_PHYS_TO_VIRT(_multiboot_info);
process_zbi(reinterpret_cast<zbi_header_t*>(X86_PHYS_TO_VIRT(mod->mod_start)),mod->mod_start);
process_zbi_item()
bootloader会把efi mem map信息放到ramdisk的前面: bootloader/src/zircon.c
pc_init_debug_early();
handle_serial_cmdline()
bootloader.uart.type = ZBI_UART_PC_PORT;
bootloader.uart.base = 0x3f8;
bootloader.uart.irq = ISA_IRQ_SERIAL1;
uart_io_port = static_cast<uint32_t>(bootloader.uart.base);
uart_irq = bootloader.uart.irq;
platform_early_display_init();
gfxconsole_bind_display(&info, bits);
gfx_init_surface_from_display(&hw, info)
gfx_init_surface(surface, info->framebuffer, info->width, info->height, info->stride, info->format, flags);
gfx_init_surface(&sw_surface, raw_sw_fb, hw_surface.width,hw_surface.height, hw_surface.stride, hw_surface.format, 0)
注册一些画图的函数
gfxconsole_setup(s, &hw_surface);
gfxconsole_clear(false);
boot_reserve_init();
boot_reserve_add_range(get_kernel_base_phys(), get_kernel_size());
platform_preserve_ramdisk();
pc_mem_init();
platform_mem_range_init()
efi_range_init(&range, &efi_seq);
mem_arena_init(&range);
这里通过UEFI提供的信息初始化物理内存区域。具体参见UEFI标准和bootloader传递memory map
信息的代码。
efi_range_init(&range, &efi_seq)
e820_range_init(&range, &e820_seq)
multiboot_range_init(&range, &multiboot_seq)
这里会把内存区域信息存一份。后面x86_pcie_init_hook()会用到
boot_reserve_wire();
pmm_alloc_range(res[i].pa, pages, &reserved_page_list);
// walk through the arenas, looking to see if the physical page belongs to it
把之前保留的物理页标记成wired
lk_primary_cpu_init_level(LK_INIT_LEVEL_PLATFORM_EARLY, LK_INIT_LEVEL_TARGET_EARLY - 1);
counters_init()
target_early_init();
none
lk_primary_cpu_init_level(LK_INIT_LEVEL_TARGET_EARLY, LK_INIT_LEVEL_VM_PREHEAP - 1);
crypto::GlobalPRNG::EarlyBootSeed
vm_init_preheap();
VmAspace::KernelAspaceInitPreHeap();
static VmAspace _kernel_aspace;
static VmAddressRegionDummy dummy_vmar;
static VmAddressRegion _kernel_root_vmar(_kernel_aspace);
_kernel_aspace.root_vmar_ = fbl::AdoptRef(&_kernel_root_vmar);
_kernel_aspace.Init();
arch_aspace_.Init(base_, size_, arch_aspace_flags);
X86ArchVmAspace::Init(vaddr_t base, size_t size, uint mmu_flags)
mmu = new (page_table_storage_) X86PageTableMmu();
mmu->InitKernel(this);
ctx_ = ctx; X86ArchVmAspace
pages_ = 1;
使用start.S里设置的页表
// mark the physical pages used by the boot time allocator
// grab a page and mark it as the zero page
lk_primary_cpu_init_level(LK_INIT_LEVEL_VM_PREHEAP, LK_INIT_LEVEL_HEAP - 1);
none
heap_init();
cmpct_init();
heap_grow(initial_alloc);
heap_page_alloc(size >> PAGE_SIZE_SHIFT);
pmm_alloc_contiguous(pages, 0, PAGE_SIZE_SHIFT, &pa, &list);
分配物理页给内核堆
lk_primary_cpu_init_level(LK_INIT_LEVEL_HEAP, LK_INIT_LEVEL_VM - 1);
console_init()
init_history();
history = calloc(1, HISTORY_LEN * LINE_LEN);
history_next = 0;
vm_init();
首先把kernel自己占用的虚拟地址区域保留起来。
保留时只是保留虚拟地址区域,并没有做页表映射,因为页表已经在start.S里映射好了。
ProtectRegion(aspace, region->base, region->arch_mmu_flags);
vm_mapping->Protect(vm_mapping->base(), vm_mapping->size(), arch_mmu_flags);
ProtectLocked(base, size, new_arch_mmu_flags);
ProtectOrUnmap(aspace_, base, size, new_arch_mmu_flags);
aspace->arch_aspace().Protect(base, size / PAGE_SIZE, new_arch_mmu_flags);
pt_->ProtectPages(vaddr, count, mmu_flags);
UpdateMapping(virt_, mmu_flags, top_level(), start, &result, &cm);
aspace->ReserveSpace("physmap", PHYSMAP_SIZE, PHYSMAP_BASE);
PHYSMAP_BASE = 0xffffff8000000000UL # -512GB
保留一部分虚拟地址区域,对应于一些有用的物理地址。x86是64gb, arm64是512gb
// The kernel physmap is a region of the kernel where all of useful physical memory
// is mapped in one large chunk. It's up to the individual architecture to decide
// how much to map but it's usually a fairly large chunk at the base of the kernel
// address space.
目的是一些需要用到物理地址的情况,比如设备内存映射,可以直接在虚拟地址和物理地址之间建立关系。
va = pa + PHYSMAP_BASE
kernel heap也是利用这个映射区域。
内核自己要用的其他虚拟内存,比如后面的Handle arena,都在这个区域之后映射。
之所以Handle arena不用physmap, 是因为它用了vmo lazy commit, 用了vmo就无法用physmap.
而kernel heap不需要用vmo来back up它。
trim_to_aspace(*this, vaddr, size);
检查溢出的情况
VmObjectPaged::Create(PMM_ALLOC_FLAG_ANY, 0, &vmo);
创建一个长度为0的vmo
MapObjectInternal(fbl::move(vmo), name, 0, size, &ptr, 0, VMM_FLAG_VALLOC_SPECIFIC,
arch_mmu_flags);
offset = 0, size = 64gb, ptr = KERNEL_BASE
vmar_offset = reinterpret_cast<vaddr_t>(*ptr);
vmar_offset -= base_; // 0
RootVmar()->CreateVmMapping(vmar_offset, size, align_pow2,
vmar_flags,
vmo, offset, arch_mmu_flags, name, &r);
CreateSubVmarInternal(mapping_offset, size, align_pow2, vmar_flags, fbl::move(vmo),
vmo_offset, arch_mmu_flags, name, &res);
vmar = fbl::AdoptRef(new (&ac) VmMapping(*this, new_base, size, vmar_flags,
fbl::move(vmo), vmo_offset, arch_mmu_flags));
vmar->Activate();
parent_->subregions_.insert(fbl::RefPtr<VmAddressRegionOrMapping>(this));
no commit.
lk_primary_cpu_init_level(LK_INIT_LEVEL_VM, LK_INIT_LEVEL_KERNEL - 1)
pmm_enforce_fill(uint level)
pmm_node.EnforceFill();
用0x42填充内存
platform_ensure_display_memtype
//initialize ACPI tables as soon as we have a working VM
platform_init_acpi_tables(uint level)
This function enables *only* the ACPICA Table Manager subsystem.
The rest of the ACPI subsystem will remain uninitialized.
AcpiInitializeTables(acpi_tables, ACPI_MAX_INIT_TABLES, FALSE);
acpi在拿到rsdp的物理地址后,会把它映射到一个新的虚拟地址上。
acpi table manager可以独立初始化
//Begin running after ACPI tables are up
hpet_init(uint level)
为hept映射一个页的地址
platform_init_apic(uint level)
通过设置LVT可以指定各种中断的向量号
把8259a关掉
platform_enumerate_io_apics(NULL, 0, &num_io_apics);
acpi_get_madt_record_limits(&records_start, &records_end);
获得ioapic的信息
apic_vm_init();
// Create a mapping for the page of MMIO registers
apic_local_init();
x2apic_enabled = true;
// Enter xAPIC or x2APIC mode and set the base address
apic_io_init(io_apics, num_io_apics, isos, num_isos);
为每个ioapic映射物理地址
global irq是系统给硬件配置的中断号,acpi会配置这个。
io apic会把global irq映射到x86 vector,一个新的中断号。这个中断号是cpu
看到的,调用哪个中断handler是根据这个中断号来的。
register_int_handler(unsigned int vector, int_handler handler, void* arg)
这个函数的逻辑是:vector是global irq, 先检查其对应的x86 vector是否合法,
如果不合法则分配一个新的x86 vector, 然后把handler配置到x86 vector上,
最后在io apic里配置global irq到x86 vector的重定向。
pc_init_timer(uint level)
kernel_init();
mp_init();
thread_init();
timer_queue_init();
// nothing interesting
lk_primary_cpu_init_level(LK_INIT_LEVEL_KERNEL, LK_INIT_LEVEL_THREADING - 1);
dlog_init_hook
thread_set_priority_experiment_init_hook
crypto::GlobalPRNG::BecomeThreadSafe
bootstrap2 thread:
lk_primary_cpu_init_level(LK_INIT_LEVEL_THREADING, LK_INIT_LEVEL_ARCH - 1);
dpc_init
object_glue_init(uint level)
Handle::Init();
arena_.Init("handles", sizeof(Handle), kMaxHandleCount);
VmObjectPaged::Create(PMM_ALLOC_FLAG_ANY, vmo_sz, &vmo);
root_vmar->CreateSubVmar(0, // offset (ignored)
vmar_sz,
false, // align_pow2
VMAR_FLAG_CAN_MAP_READ |
VMAR_FLAG_CAN_MAP_WRITE |
VMAR_FLAG_CAN_MAP_SPECIFIC,
vname, &vmar);
AllocSpotLocked(size, align_pow2, arch_mmu_flags, &new_base);
LinearRegionAllocatorLocked(size, align_pow2, arch_mmu_flags, spot);
CheckGapLocked(before_iter, after_iter, spot, base, align, size, 0, arch_mmu_flags)
arena映射的是之前kernel root_vmar保留区域之后的区域。
vmar->CreateVmMapping(0, // mapping_offset
control_mem_sz,
false, // align_pow2
VMAR_FLAG_SPECIFIC,
vmo,
0, // vmo_offset
ARCH_MMU_FLAG_PERM_READ |
ARCH_MMU_FLAG_PERM_WRITE,
"control", &control_mapping);
arch_init();
x86_feature_debug();
x86_mmu_init();
idt_setup_readonly();
x86_perfmon_init();
x86_processor_trace_init();
// nothing interesting
lk_primary_cpu_init_level(LK_INIT_LEVEL_ARCH, LK_INIT_LEVEL_PLATFORM - 1);
// none
platform_init();
platform_init_smp();
x86_init_smp(apic_ids.get(), num_cpus);
lk_init_secondary_cpus(num_cpus - 1);
x86_bringup_aps(apic_ids.get(), num_cpus - 1);
x86_bootstrap16_acquire((uintptr_t)_x86_secondary_cpu_long_mode_entry,
&bootstrap_aspace, (void**)&bootstrap_data,
&bootstrap_instr_ptr);
apic_send_ipi(vec, apic_id, DELIVERY_MODE_STARTUP);
// The effect is to set CS:IP to VV00:0000h
// AP 实模式的启动代码是之前扫描内存时选择的低于1MB的一个地方,启动数据在4kb之后。
// far return的时候,%esp指向bootstrap data里面的phys_long_mode_entry
lk_primary_cpu_init_level(LK_INIT_LEVEL_PLATFORM, LK_INIT_LEVEL_TARGET - 1);
x86_pcie_init_hook()
PcieBusDriver::InitializeDriver(platform_pcie_support);
driver_ = fbl::AdoptRef(new (&ac) PcieBusDriver(platform));
driver_->AllocBookkeeping();
pcie = PcieBusDriver::GetDriver();
return driver_;
constexpr uint64_t pcie_pio_base = 0x8000;
constexpr uint64_t pcie_pio_size = 0x10000 - pcie_pio_base;
pcie->AddBusRegion(pcie_pio_base, pcie_pio_size, PciAddrSpace::PIO);
pcie->AddBusRegion(pcie_mmio_base, pcie_mmio_size, PciAddrSpace::MMIO);
RegionAllocator::AddRegion(const ralloc_region_t& region, bool allow_overlap) {
然后去掉那些已经被其他设备占用的物理地址范围,包括物理内存。结束。
target_init();
lk_primary_cpu_init_level(LK_INIT_LEVEL_TARGET, LK_INIT_LEVEL_LAST);
userboot_init(uint level)
-----------
以下是老内容。
bootdata_paddr存放的是initrd的加载地址0x48000000。boot-shim会把从device tree
中读出来的initrd的加载地址写入bootdata_paddr。此外,qemu会把内核加载到0x40080000处。
内核的实际物理地址就是0x40090000。这些细节需要从qemu的源码中获得。
boot_reserve_add_range(get_kernel_base_phys(), get_kernel_size())
把kernel占用的物理地址区域保留起来,也就是0x40090000开始的一块区域。
接下来会处理append_board_bootdata()里加入的一些bootdata的section.
然后到pdev_init()里调用各个周边设备驱动的初始化代码。
把ramdisk所在的0x48000000开始的一块内存保留起来。
kernel.memory-limit-mb这个命令行参数应该是没有设置的。
在每个物理内存mem arena的尾部放置的是一堆描述page的vm_page_t。
把之前标记为保留的内存区域在arena中保留出来,标记成WIRED。
platform_early_init()结束。
终于来到了"welcome to Zircon"。
接下来调用crypto::GlobalPRNG::EarlyBootSeed(),初始化一些随机数生成器相关的东西。
进入vm_init_preheap(),初始化一个唯一的内核虚拟地址空间。
把boot time allocator用掉的内存(主要是分配给translation table)在物理内存管理pmm里标记为已用。
随机保留分配一些页面。可能是为了增加内存随机性。
用dc zva把缓存置零。
进入heap_init(),把内核要使用的heap page申请出来。
进入vm_init()。为内核占用的虚拟地址区域创建VmObject. VmMapping好像只是记录一个vmo有哪些flags.
在创建VmMapping之后,
Activate()的时候才把它插到parent,也就是RootVmar的ChildList里面。
platform_init_postvm()把外围设备的虚拟地址保留起来。
kernel_init(), 初始化多处理器的一些状态。初始化每个处理器的timer. 之后,内核就可以创建线程了。缺省的线程stack大小是8k.
线程执行轨迹的切换通过改变x30完成。x30是link register,
存储的是arm64_context_switch()函数调用语句的下一条指令的地址。
当在arm64_context_switch()里面切换了sp之后,新的x30从新线程的栈上pop出来。
ret指令接着x30存储的地址继续执行。实际上,每个线程的x30存储的地址都是一样的,因为
arm64_context_switch()只在一个地方被调用。
bootstrap2.
初始化deferred procedure call (dpc)线程。
初始化Handle和Arena. 这个过程没有page committed。内存页真正分配出去是在做Arena::Pool::Pop()的时候。
在VmMappingCoalescer::Flush()里会建立物理页表的映射。在做VmMapping的时候,如果不指定起始地址,
则会去找一个gap地址。
PortDispatcher暂时不知道干啥的。
进入arch_init()
给每个cpu创建一个idle thread. 释放副cpu的spin lock.
进入platform_init(). 给每个副cpu创建kernel stack, 是一个VmMapping.
pdev_run_hooks()
moving to last init leve.
user level init: ktrace, console, userboot.
ktrace: 给它分配一块内存,缺省为32MB
kernel console通过uart读命令。
\end{verbatim}