操作系统的内核映像

构建内核映像

  • GNU工具链
    • 自动化构建工具 –> make
    • 编译器 –> gcc
  • 构建内核映像
    • 编写Makefile,使用自动化构建工具make
      • Makefile的缩进必须使用制表符(tab键),而不是空格
      • make –> 构建内核,包括编译、汇编、链接等
      • make run –> 运行内核
      • make clean –> 清理文件
      • 因为run、clean不是真正的构建命令,所以将其作为伪命令.PHONY
    • 内核映像的组成部分
      • boot16.bin –> 16位模式,或者实模式
      • boot32.bin –> 32位模式,或者保护模式
    • 链接地址
      • boot16.bin –> 加载地址0x10000,实模式基址0x10000,所以链接地址-Ttext=0x0
      • boot32.bin –> 加载地址0x20000,保护模式基址0x0,所以链接地址-Ttext=0x20000
    • 内核映像的组装
      • kernel.bin为目标,boot16.bin、boot32.bin为依赖
        • 使用dd命令,输入文件为boot16.bin,输出文件为kernel.bin,块大小为1B,不截断
        • 使用dd命令,输入文件为boot32.bin,输出文件为kernel.bin,块大小为1B,不截断,偏移为64K = 0x20000 – 0x10000
      • boot16.bin为目标,boot16.S为依赖
      • boot32.bin为目标,boot32.S为依赖

// ----- Makefile -----

// Build kernel image
kernel.bin: boot16.bin boot32.bin
    dd if=boot16.bin of=kernel.bin bs=1 conv=notrunc
    dd if=boot32.bin of=kernel.bin bs=1 conv=notrunc seek=64K

// Real mode
boot16.bin: boot16.S
    gcc -c boot16.S -o boot16.o
    ld boot16.o -Ttext=0x0 -o boot16.elf
    objcopy boot16.elf -O binary boot16.bin

// Protected mode
boot32.bin: boot32.S
    gcc -c boot32.S -o boot32.o
    ld boot32.o -Ttext=0x20000 -o boot32.elf
    objcopy boot32.elf -O binary boot32.bin

.PHONY: run clean

// Run kvmtool
run: kernel.bin
    ./kvmtool/lkvm run -c 1 -k kernel.bin

// Remove files
clean:
    rm -f *.o *.elf *.bin

实模式、保护模式、64位模式

  • 三种模式的内存寻址
    • 实模式 –> 段寄存器、段偏移,寻址范围为24 * 216B = 1MB
    • 保护模式 –> 段描述符,寻址范围为232B = 4GB
    • 64位模式 –> 虚拟内存的分页,寻址范围为264B = 16EB
    • 实模式可以访问所有内存,保护模式只能访问受保护的内存。二者只是快速过渡,以保证X86指令集的内存寻址从16位到64位是兼容的,最终需要进入64位模式开启虚拟内存的分页
  • 实模式
    • 操作系统启动(Boot)之后,代码段寄存器CS = 0x1000,指令指针寄存器IP = 0x0000。因此,第一条指令的内存地址计算如下
      • 基址 –> CS << 4,偏移 –> IP
      • 0x1000 << 4 + 0x0000 = 0x10000
    • 内存布局0x0~0xFFFFF(1MB)
      • 0x0~0xFFFF(64KB)
        • 中断向量表、BIOS数据区、操作系统加载器
      • 0x10000~0x1FFFF(128KB)
        • boot16.bin
      • 0x20000~0x7FFFF(512KB)
        • boot32.bin
      • 0x80000~0xFFFFF(1MB)
        • 扩展BIOS数据区、显存、显卡BIOS、扩展BIOS、主板BIOS
      • 0x100000~
        • 64位模式部分
  • 从实模式到保护模式
    • 关闭中断(cli指令)
    • 加载段描述符表(lgdt指令)
      • 段描述符表长度为gdt_end – gdt,地址为0x1000 << 4 + gdt,它们都是.word数据(字,16位)
      • 第一个段描述符为0,第二个段描述符为代码段,第三个段描述符为数据段,它们都是.quad数据(四字,64位)
      • 段描述符包含基址等信息,比如我们将保护模式的基址,全部设置为0x0
    • 开启保护模式(控制寄存器CR0)
      • 最低位PE置1,开启保护模式
    • 长跳转到保护模式的代码(ljmpl指令)
      • 此时已经为保护模式,需要按照段描述符寻址
      • ljmpl指令的第一个参数为段选择子 –> 代码段索引(1)、全局描述符(0)、内核特权级(00),故段选择子为0x8
      • 指令后缀表示操作的位数,比如movw(w,16位)、ljmpl(l,32位)、pushq(q,64位)
// ----- boot16.S -----

.text
.code16
.start16:
    // Clear interrupt flag
    cli

    // Load global descriptor table
    lgdt gdtr

    // Protected mode enable (PE = 1 in CR0)
    mov %cr0, %eax
    or $0x1, %eax
    mov %eax, %cr0

    // Long jump to boot32
    ljmpl $0x8, $0x20000

gdt:
    .quad 0x0000000000000000
    .quad 0x00c09a00000007ff  // Code
    .quad 0x00c09200000007ff  // Data
gdt_end:

gdtr:
    .word gdt_end - gdt
    .word gdt, 0x1



// ----- boot32.S -----

.text
.code32
.start32:
    mov $'A', %al
    mov $0x3f8, %dx
    out %al, %dx
    hlt



// ----- Run Program -----

// Build kernel image
$ make

// Run kvmtool
$ make run
A