1// 这是内核执行头程序 2// Created by longjin. 3// 2022/01/20 4 5#include "common/asm.h" 6 7// 以下是来自 multiboot2 规范的定义 8// How many bytes from the start of the file we search for the header. 9#define MULTIBOOT_SEARCH 32768 10#define MULTIBOOT_HEADER_ALIGN 8 11 12// The magic field should contain this. 13#define MULTIBOOT2_HEADER_MAGIC 0xe85250d6 14 15// This should be in %eax. 16#define MULTIBOOT2_BOOTLOADER_MAGIC 0x36d76289 17 18// Alignment of multiboot modules. 19#define MULTIBOOT_MOD_ALIGN 0x00001000 20 21// Alignment of the multiboot info structure. 22#define MULTIBOOT_INFO_ALIGN 0x00000008 23 24// Flags set in the 'flags' member of the multiboot header. 25 26#define MULTIBOOT_TAG_ALIGN 8 27#define MULTIBOOT_TAG_TYPE_END 0 28#define MULTIBOOT_TAG_TYPE_CMDLINE 1 29#define MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME 2 30#define MULTIBOOT_TAG_TYPE_MODULE 3 31#define MULTIBOOT_TAG_TYPE_BASIC_MEMINFO 4 32#define MULTIBOOT_TAG_TYPE_BOOTDEV 5 33#define MULTIBOOT_TAG_TYPE_MMAP 6 34#define MULTIBOOT_TAG_TYPE_VBE 7 35#define MULTIBOOT_TAG_TYPE_FRAMEBUFFER 8 36#define MULTIBOOT_TAG_TYPE_ELF_SECTIONS 9 37#define MULTIBOOT_TAG_TYPE_APM 10 38#define MULTIBOOT_TAG_TYPE_EFI32 11 39#define MULTIBOOT_TAG_TYPE_EFI64 12 40#define MULTIBOOT_TAG_TYPE_SMBIOS 13 41#define MULTIBOOT_TAG_TYPE_ACPI_OLD 14 42#define MULTIBOOT_TAG_TYPE_ACPI_NEW 15 43#define MULTIBOOT_TAG_TYPE_NETWORK 16 44#define MULTIBOOT_TAG_TYPE_EFI_MMAP 17 45#define MULTIBOOT_TAG_TYPE_EFI_BS 18 46#define MULTIBOOT_TAG_TYPE_EFI32_IH 19 47#define MULTIBOOT_TAG_TYPE_EFI64_IH 20 48#define MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR 21 49 50#define MULTIBOOT_HEADER_TAG_END 0 51#define MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST 1 52#define MULTIBOOT_HEADER_TAG_ADDRESS 2 53#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS 3 54#define MULTIBOOT_HEADER_TAG_CONSOLE_FLAGS 4 55#define MULTIBOOT_HEADER_TAG_FRAMEBUFFER 5 56#define MULTIBOOT_HEADER_TAG_MODULE_ALIGN 6 57#define MULTIBOOT_HEADER_TAG_EFI_BS 7 58#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI32 8 59#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI64 9 60#define MULTIBOOT_HEADER_TAG_RELOCATABLE 10 61 62#define MULTIBOOT_ARCHITECTURE_I386 0 63#define MULTIBOOT_ARCHITECTURE_MIPS32 4 64#define MULTIBOOT_HEADER_TAG_OPTIONAL 1 65 66#define MULTIBOOT_LOAD_PREFERENCE_NONE 0 67#define MULTIBOOT_LOAD_PREFERENCE_LOW 1 68#define MULTIBOOT_LOAD_PREFERENCE_HIGH 2 69 70#define MULTIBOOT_CONSOLE_FLAGS_CONSOLE_REQUIRED 1 71#define MULTIBOOT_CONSOLE_FLAGS_EGA_TEXT_SUPPORTED 2 72 73 74 75// 直接用 -m64 编译出来的是 64 位代码, 76// 但是启动后的机器是 32 位的,相当于在 32 位机器上跑 64 位程序。 77// 得加一层跳转到 64 位的 -m32 代码,开启 long 模式后再跳转到以 -m64 编译的代码中 78// 对于 x86_64,需要在启动阶段进入长模式(IA32E),这意味着需要一个临时页表 79// See https://wiki.osdev.org/Creating_a_64-bit_kernel: 80// With a 32-bit bootstrap in your kernel 81 82// 这部分是从保护模式启动 long 模式的代码 83// 工作在 32bit 84// 声明这一段代码以 32 位模式编译 85.code32 86 87// multiboot2 文件头 88// 计算头长度 89.SET HEADER_LENGTH, multiboot_header_end - multiboot_header 90// 计算校验和 91.SET CHECKSUM, -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + HEADER_LENGTH) 92// 8 字节对齐 93.section .multiboot_header 94.align MULTIBOOT_HEADER_ALIGN 95// 声明所属段 96 97multiboot_header: 98 // 魔数 99 .long MULTIBOOT2_HEADER_MAGIC 100 // 架构 101 .long MULTIBOOT_ARCHITECTURE_I386 102 // 头长度 103 .long HEADER_LENGTH 104 // 校验和 105 .long CHECKSUM 106 // 添加其它内容在此,详细信息见 Multiboot2 Specification version 2.0.pdf 107 108// 设置帧缓冲区(同时在这里设置qemu的分辨率, 默认为: 1440*900, 还支持: 640*480, 等) 109.align 8 110framebuffer_tag_start: 111 .short MULTIBOOT_HEADER_TAG_FRAMEBUFFER 112 .short MULTIBOOT_HEADER_TAG_OPTIONAL 113 .long framebuffer_tag_end - framebuffer_tag_start 114 .long 1440 // 宽 115 .long 900 // 高 116 .long 32 117framebuffer_tag_end: 118.align 8 119 .short MULTIBOOT_HEADER_TAG_END 120 // 结束标记 121 .short 0 122 .long 8 123multiboot_header_end: 124 125.section .bootstrap 126 127.global _start 128.type _start, @function 129# 在 multiboot2.cpp 中定义 130 131.extern _start64 132.extern boot_info_addr 133.extern multiboot2_magic 134ENTRY(_start) 135 // 关中断 136 cli 137 138 // multiboot2_info 结构体指针 139 mov %ebx, mb2_info 140 //mov %ebx, %e8 141 // 魔数 142 mov %eax, mb2_magic 143 144 //mov %eax, %e9 145 / 从保护模式跳转到长模式 146 // 1. 允许 PAE 147 mov %cr4, %eax 148 or $(1<<5), %eax 149 mov %eax, %cr4 150 // 2. 设置临时页表 151 // 最高级 152 mov $pml4, %eax 153 mov $pdpt, %ebx 154 or $0x3, %ebx 155 mov %ebx, 0(%eax) 156 157 // 次级 158 mov $pdpt, %eax 159 mov $pd, %ebx 160 or $0x3, %ebx 161 mov %ebx, 0(%eax) 162 163 // 次低级 164 mov $pd, %eax 165 mov $pt, %ebx 166 or $0x3, %ebx 167 mov %ebx, 0(%eax) 168 169 // 最低级 170 // 循环 512 次,填满一页 171 mov $512, %ecx 172 mov $pt, %eax 173 mov $0x3, %ebx 174.fill_pt: 175 mov %ebx, 0(%eax) 176 add $0x1000, %ebx 177 add $8, %eax 178 loop .fill_pt 179 180.global enter_head_from_ap_boot 181enter_head_from_ap_boot: 182 // 填写 CR3 183 mov $pml4, %eax 184 mov %eax, %cr3 185 186 // 3. 切换到 long 模式 187 mov $0xC0000080, %ecx 188 rdmsr 189 or $(1<<8), %eax 190 wrmsr 191 192 // 4. 开启分页 193 mov %cr0, %eax 194 or $(1<<31), %eax 195 mov %eax, %cr0 196 197 // 5. 重新设置 GDT 198 mov $gdt64_pointer, %eax 199 lgdt 0(%eax) 200 201 jmp $0x8, $ready_to_start_64 202 hlt 203 ret 204.code64 205.global ready_to_start_64 206ready_to_start_64: 207 208 mov $0x10, %ax 209 mov %ax, %ds 210 mov %ax, %es 211 mov %ax, %fs 212 mov %ax, %ss 213 mov $0x7e00, %esp 214 215 216 //6. 跳转到start64 217 movq switch_to_start64(%rip), %rax 218 pushq $0x08 //段选择子 219 pushq %rax 220 lretq 221 222switch_to_start64: 223 .quad _start64 224 225 226.code64 227is_from_ap: 228 229 hlt 230 231.global _start64 232.type _start64, @function 233.extern Start_Kernel 234ENTRY(_start64) 235 236 237 // 初始化寄存器 238 mov $0x10, %ax 239 mov %ax, %ds 240 mov %ax, %es 241 mov %ax, %fs 242 mov %ax, %ss 243 mov $0x7e00, %esp 244 245// === 加载GDTR ==== 246 lgdt GDT_POINTER(%rip) //这里我没搞明白rip相对寻址, 看了文档,大概是用来实现PIC的(position independent code) 247 //lgdt $GDT_POINTER 248// === 加载IDTR ==== 249 lidt IDT_POINTER(%rip) 250 //lidt $IDT_POINTER 251 movq GDT_POINTER(%rip), %r12 252 movq head_stack_start(%rip), %rsp 253 254 // 分支,判断是否为apu 255 movq $0x1b, %rcx // 根据IA32_APIC_BASE.BSP[8]标志位判断处理器是否为apu 256 rdmsr 257 bt $8, %rax 258 jnc load_apu_cr3 259 260 // 2. 设置临时页表 261 // 最高级 262 mov $__PML4E, %eax 263 mov $__PDPTE, %ebx 264 or $0x3, %ebx 265 mov %ebx, 0(%eax) 266 267 mov $__PML4E, %eax 268 // 加256个表项, 映射高地址 269 add $2048, %eax 270 mov %ebx, 0(%eax) 271 272 // 次级 273 mov $__PDPTE, %eax 274 mov $__PDE, %ebx 275 or $0x3, %ebx 276 mov %ebx, 0(%eax) 277 278 // 次低级 279 mov $__PDE, %eax 280 mov $50, %ecx 281 mov $__PT_S, %ebx 282 or $0x3, %ebx 283.fill_pde_64: 284 mov %ebx, 0(%eax) 285 add $0x1000, %ebx 286 add $8, %eax 287 loop .fill_pde_64 288 289 // 最低级 290 // 循环 512*50=25600 次,填满50页 291 mov $25600, %ecx 292 mov $__PT_S, %eax 293 mov $0x3, %ebx 294.fill_pt_64: 295 mov %ebx, 0(%eax) 296 add $0x1000, %ebx 297 add $8, %eax 298 loop .fill_pt_64 299 300 301// ==== 加载CR3寄存器 302 303load_cr3: 304 305 movq $__PML4E, %rax //设置页目录基地址 306 307 movq %rax, %cr3 308 jmp to_switch_seg 309 310load_apu_cr3: 311 // 由于内存管理模块重置了页表,因此ap核心初始化的时候,需要使用新的内核页表。 312 // 这个页表的值由smp模块设置到__APU_START_CR3变量中 313 314 // 加载__APU_START_CR3中的值 315 movq $__APU_START_CR3, %rax 316 movq 0(%rax), %rax 317 movq %rax, %cr3 318 jmp to_switch_seg 319 320to_switch_seg: 321 322 movq switch_seg(%rip), %rax 323 // 由于ljmp和lcall在GAS中不受支持,因此我们需要先伪造函数调用现场,通过lret的方式,给它跳转过去。才能更新cs寄存器 324 // 实在是太妙了!Amazing! 325 pushq $0x08 //段选择子 326 pushq %rax 327 lretq 328 329// 64位模式的代码 330switch_seg: 331 332 .quad entry64 333 334 335entry64: 336 337 movq $0x10, %rax 338 movq %rax, %ds 339 movq %rax, %es 340 movq %rax, %gs 341 movq %rax, %ss 342 343 movq head_stack_start(%rip), %rsp //rsp的地址 344 345 // 重新加载GDT和IDT,加载到高地址 346 leaq GDT_Table(%rip), %r8 347 leaq GDT_END(%rip), %r9 348 349 subq %r8, %r9 350 movq %r9, %r13 // GDT size 351 352 leaq IDT_Table(%rip), %r8 353 leaq IDT_END(%rip), %r9 354 355 subq %r8, %r9 356 movq %r9, %r12 // IDT size 357 358 lgdt GDT_POINTER64(%rip) 359 lidt IDT_POINTER64(%rip) 360 361 // 分支,判断是否为apu 362 movq $0x1b, %rcx // 根据IA32_APIC_BASE.BSP[8]标志位判断处理器是否为apu 363 rdmsr 364 bt $8, %rax 365 jnc start_smp 366 367setup_IDT: 368 // 该部分代码只在启动初期使用,后面的c文件中会重新设置IDT, 369 leaq m_ignore_int(%rip), %rdx // 将ignore_int的地址暂时存到中段描述符的高8B 370 movq $(0x08 << 16), %rax // 设置段选择子。由IDT结构和段选择子结构可知,本行设置段基地址为0x100000,TI=0,RPL=0 371 movw %dx, %ax 372 373 movq $ (0x8e00 << 32), %rcx // 设置Type=1110 P=1 DPL=00 0=0 374 addq %rcx, %rax 375 376 // 把ignore_int的地址填写到正确位置, rax存低8B, rdx存高8B 377 movl %edx, %ecx 378 shrl $16, %ecx // 去除低16位 379 shlq $48, %rcx 380 addq %rcx, %rax // 填写段内偏移31:16 381 382 shrq $32, %rdx // (已经填写了32位,故右移32) 383 384 leaq IDT_Table(%rip), %rdi // 获取中断描述符表的首地址,存储到rdi 385 mov $256, %rcx // 初始化每个中断描述符 386 387repeat_set_idt: 388 // ====== 循环,初始化总共256个中断描述符 === 389 movq %rax, (%rdi) // 保存低8B 390 movq %rdx, 8(%rdi) // 保存高8B 391 392 addq $0x10, %rdi // 转到下一个IDT表项 393 dec %rcx 394 jne repeat_set_idt 395 396SetUp_TSS64: 397 // == 设置64位的任务状态段表 === 398 //rdx保存高8B, rax保存低8B 399 leaq TSS64_Table(%rip), %rdx // 获取定义在process.c中的initial_tss[0]的地址 400 401 movq $0xffff800000000000, %r8 402 addq %r8, %rdx 403 404 xorq %rax, %rax 405 xorq %rcx, %rcx 406 407 // 设置TSS描述符的47:40位为1000 1001 408 movq $0x89, %rax 409 shlq $40, %rax 410 411 // 设置段基地址31:24 412 movl %edx, %ecx 413 shrl $24, %ecx 414 shlq $56, %rcx 415 addq %rcx, %rax 416 417 xorq %rcx, %rcx 418 419 // 设置段基地址23:00 420 movl %edx, %ecx 421 andl $0xffffff, %ecx // 清空ecx的中有效值的高8位(也就是上面已经赋值了的) 422 shlq $16, %rcx 423 addq %rcx, %rax 424 425 addq $103, %rax // 设置段长度 426 427 leaq GDT_Table(%rip), %rdi 428 movq %rax, 80(%rdi) // 把低八B存储到GDT第10项 429 shrq $32, %rdx 430 movq %rdx, 88(%rdi) // 高8B存到GDT第11项 431 432 // 装载任务状态段寄存器(已改为在main.c中使用load_TR宏进行装载) 433 // mov $0x50, %ax // 设置起始地址为80 434 // ltr %ax 435 436 //now enable SSE and the like 437 movq %cr0, %rax 438 and $0xFFFB, %ax //clear coprocessor emulation CR0.EM 439 or $0x2, %ax //set coprocessor monitoring CR0.MP 440 movq %rax, %cr0 441 movq %cr4, %rax 442 or $(3 << 9), %ax //set CR4.OSFXSR and CR4.OSXMMEXCPT at the same time 443 movq %rax, %cr4 444 445 446 447 movq go_to_kernel(%rip), %rax /* movq address */ 448 pushq $0x08 449 pushq %rax 450 451 452 453 movq mb2_info, %r15 454 movq mb2_magic, %r14 455 456 457 lretq 458 459go_to_kernel: 460 .quad Start_Kernel 461 462start_smp: 463 464 465 //now enable SSE and the like 466 movq %cr0, %rax 467 and $0xFFFB, %ax //clear coprocessor emulation CR0.EM 468 or $0x2, %ax //set coprocessor monitoring CR0.MP 469 movq %rax, %cr0 470 movq %cr4, %rax 471 or $(3 << 9), %ax //set CR4.OSFXSR and CR4.OSXMMEXCPT at the same time 472 movq %rax, %cr4 473 474 475 movq go_to_smp_kernel(%rip), %rax /* movq address */ 476 pushq $0x08 477 pushq %rax 478 479/* 480 // 重新加载GDT和IDT,加载到高地址 481 leaq GDT_Table(%rip), %r8 482 leaq GDT_END(%rip), %r9 483 484 subq %r8, %r9 485 movq %r9, %r13 // GDT size 486 487 leaq IDT_Table(%rip), %r8 488 leaq IDT_END(%rip), %r9 489 490 subq %r8, %r9 491 movq %r9, %r12 // IDT size 492 493 lgdt GDT_POINTER64(%rip) 494 lidt IDT_POINTER64(%rip) 495*/ 496 lretq 497 498go_to_smp_kernel: 499 500 .quad smp_ap_start 501 502// ==== 异常/中断处理模块 ignore int: 忽略中断 503// (该部分代码只在启动初期使用,后面的c文件中会重新设置IDT,从而重设ignore_int的中断入点) 504m_ignore_int: 505// 切换到c语言的ignore_int 506 movq go_to_ignore_int(%rip), %rax 507 pushq $0x08 508 pushq %rax 509 lretq 510 511 512 513go_to_ignore_int: 514 .quad ignore_int_handler 515 516 517ENTRY(head_stack_start) 518 .quad initial_proc_union + 32768 519 520 521// 初始化页表 522.align 0x1000 //设置为4k对齐 523__PML4E: 524 .skip 0x1000 525__PDPTE: 526 .skip 0x1000 527 528// 三级页表 529__PDE: 530 .skip 0x1000 531 532// 预留50个四级页表,总共表示100M的内存空间。这50个页表占用200KB的空间 533__PT_S: 534 .skip 0x32000 535 536 537.global __APU_START_CR3 538__APU_START_CR3: 539 .quad 0 540 541// GDT表 542 543.align 16 544.global GDT_Table // 使得GDT可以被外部程序引用或者访问 545 546GDT_Table: 547 .quad 0x0000000000000000 // 0 空描述符 0x00 548 .quad 0x0020980000000000 // 1 内核64位代码段描述符 0x08 549 .quad 0x0000920000000000 // 2 内核64位数据段描述符 0x10 550 .quad 0x0000000000000000 // 3 用户32位代码段描述符 0x18 551 .quad 0x0000000000000000 // 4 用户32位数据段描述符 0x20 552 .quad 0x0020f80000000000 // 5 用户64位代码段描述符 0x28 553 .quad 0x0000f20000000000 // 6 用户64位数据段描述符 0x30 554 .quad 0x00cf9a000000ffff // 7 内核32位代码段描述符 0x38 555 .quad 0x00cf92000000ffff // 8 内核32位数据段描述符 0x40 556 .fill 100, 8, 0 // 10-11 TSS(跳过了第9段) 重复十次填充8字节的空间,赋值为0 长模式下,每个TSS长度为128bit 557GDT_END: 558 559.global GDT_POINTER 560GDT_POINTER: 561GDT_LIMIT: .word GDT_END - GDT_Table - 1 // GDT的大小 562GDT_BASE: .quad GDT_Table 563 564.global GDT_POINTER64 565GDT_POINTER64: 566GDT_LIMIT64: .word GDT_END - GDT_Table - 1 // GDT的大小 567GDT_BASE64: .quad GDT_Table + 0xffff800000000000 568 569// IDT 表 570.global IDT_Table 571 572IDT_Table: 573 .fill 512, 8, 0 // 设置512*8字节的IDT表的空间 574IDT_END: 575 576.global IDT_POINTER 577IDT_POINTER: 578IDT_LIMIT: .word IDT_END - IDT_Table - 1 579IDT_BASE: .quad IDT_Table 580 581.global IDT_POINTER64 582IDT_POINTER64: 583IDT_LIMIT64: .word IDT_END - IDT_Table - 1 584IDT_BASE64: .quad IDT_Table + 0xffff800000000000 585 586// 64位的TSS表 587.global TSS64_Table 588 589TSS64_Table: 590 .fill 13, 8, 0 591TSS64_END: 592 593.section .bootstrap.data 594mb2_magic: .quad 0 595mb2_info: .quad 0 596 597.code32 598// 临时页表 4KB/页 599.align 0x1000 600.global pml4 601pml4: 602 .skip 0x1000 603pdpt: 604 .skip 0x1000 605pd: 606 .skip 0x1000 607pt: 608 .skip 0x1000 609 610// 临时 GDT 611.align 16 612gdt64: 613null_desc: 614 .short 0xFFFF 615 .short 0 616 .byte 0 617 .byte 0 618 .byte 0 619 .byte 0 620code_desc: 621 .short 0 622 .short 0 623 .byte 0 624 .byte 0x9A 625 .byte 0x20 626 .byte 0 627data_desc: 628 .short 0 629 .short 0 630 .byte 0 631 .byte 0x92 632 .byte 0 633 .byte 0 634user_code_desc: 635 .short 0 636 .short 0 637 .byte 0 638 .byte 0xFA 639 .byte 0x20 640 .byte 0 641user_data_desc: 642 .short 0 643 .short 0 644 .byte 0 645 .byte 0xF2 646 .byte 0 647 .byte 0 648gdt64_pointer: 649 .short gdt64_pointer-gdt64-1 650 .quad gdt64 651gdt64_pointer64: 652 .short gdt64_pointer-gdt64-1 653 .quad gdt64 654