写点什么

《操作系统实战 45 讲》笔记 1——引导部分

作者:袁世超
  • 2023-08-10
    北京
  • 本文字数:2201 字

    阅读完需:约 7 分钟

05 | CPU 工作模式:执行程序的三种模式

实模式 --> 保护模式 --> 长模式

三个模式就是 Intel CPU 的发展史,发展的主线是支持更大的内存,以及提供必要的保护机制。

虽然现在都是 64 位了,但是还得了解实模式和保护模式,这是由于兼容性的原因,在引导流程中会经历这三个模式:BIOS 工作在实模式,引导程序工作在实模式,最后 kernel 切到长模式运行,这些内容在第 10、11 讲。


保护模式中保护的含义

  • 段选择子(CS、SS)最低 2 位是 RPL(Request Privilege Level)

  • 段描述符的 45、46 位是 DPL(Descriptor Privilege Level)保护模式就是根据这两个字段实现访问权限控制。

切换到保护模式的步骤

1、准备全局段描述符表

GDT_START:knull_dsc: dq 0kcode_dsc: dq 0x00cf9e000000ffffkdata_dsc: dq 0x00cf92000000ffffGDT_END:GDT_PTR:GDTLEN  dw GDT_END-GDT_START-1GDTBASE  dd GDT_START
复制代码


第一行必须为 0。引导程序都是平坦模型,关键的几个位:

  • 55 位 G 为 1,表示段长粒度 4KB

  • 54 位 D/B 为 1,表示操作数是 32 位

  • 43 位 T,代码段 1,数据段 0

  • 43 位 C,是否可执行


2、加载 GDTR 寄存器

lgdt [GDT_PTR]
复制代码


3、设置 cr0 寄存器

;开启 PEmov eax, cr0bts eax, 0                      ; CR0.PE =1mov cr0, eax         
复制代码


4、长跳转,加载 CS 段寄存器

jmp dword 0x8 :_32bits_mode ;_32bits_mode为32位代码标号即段偏移
复制代码

0x8 换成二进制 1000,段选择子低 3 位为 TI 和 RPL,TI 为 0 表示从 GDT 中查询,偏移 1 为 kcode_dsc,再在段基的基础上偏移 _32bits_mode。


切换到长模式的步骤

1、准备全局段描述符表

ex64_GDT:null_dsc:  dq 0c64_dsc:dq 0x0020980000000000  ;64位代码段d64_dsc:dq 0x0000920000000000  ;64位数据段eGdtLen   equ $ - null_dsc  ;GDT长度eGdtPtr:dw eGdtLen - 1  ;GDT界限     dq ex64_GDT
复制代码
  • 53 位 L 为 1,表示段是 64 位模式

  • 43 位 T,同上


2、准备页表

mov eax, cr4bts eax, 5   ;CR4.PAE = 1mov cr4, eax ;开启 PAEmov eax, PAGE_TLB_BADR ;页表物理地址mov cr3, eax
复制代码

长模式下内存地址空间的保护交给了 MMU,段描述中的段基和段长已无效。

  • cr4 的第 5 位开启 PAE

  • cr3 指向页表物理地址


3、加载 GDTR 寄存器

lgdt [eGdtPtr]
复制代码

基于上面那个段描述符表,输入和输出是一样的,真正的转换落到了 MMU。


4、开启长模式

;开启 64位长模式mov ecx, IA32_EFERrdmsrbts eax, 8  ;IA32_EFER.LME =1wrmsr;开启 保护模式和分页模式mov eax, cr0bts eax, 0    ;CR0.PE =1bts eax, 31mov cr0, eax 
复制代码

IA32_EFER 寄存器的地址为 0xC0000080,有两个专门的指令进行读写。


5、加载 CS 段寄存器

jmp 08:entry64 ;entry64为程序标号即64位偏移地址
复制代码


06 | 虚幻与真实:程序中的地址如何转换?



长模式 4KB 页,每个项表 512 个条目,5125125125124KB=256TB,实际上只能寻址 48 位,这是由于 x86 CPU 只实现了 48 位的地址总线。页表本身占用空间 45128B = 16KB。


07 | Cache 与内存:程序放在哪儿?

开启 Cache

mov eax, cr0;开启 CACHE    btr eax,29 ;CR0.NW=0btr eax,30  ;CR0.CD=0mov cr0, eax
复制代码


内存视图

所有硬件都有物理地址,例如 BIOS ROM 的地址是 0xffff0,CPU 加电后 CS:IP 寄存器就会设置为该地址,以运行 BIOS 程序,而物理内存 RAM 每没有固定的地址,而该信息对于 kernel 运行至关重要,可以从 BIOS 提供的实模型下的中断服务获取内存视图。

_getmemmap:  xor ebx,ebx ;ebx设为0  mov edi,E80MAP_ADR ;edi设为存放输出结果的1MB内的物理内存地址loop:  mov eax,0e820h ;eax必须为0e820h  mov ecx,20 ;输出结果数据项的大小为20字节:8字节内存基地址,8字节内存长度,4字节内存类型  mov edx,0534d4150h ;edx必须为0534d4150h  int 15h ;执行中断  jc error ;如果flags寄存器的C位置1,则表示出错  add edi,20;更新下一次输出结果的地址  cmp ebx,0 ;如ebx为0,则表示循环迭代结束  jne loop  ;还有结果项,继续迭代    reterror:;出错处理
复制代码

在第 12 讲二级引导程序中使用了该函数。


10 | 设置工作模式与环境(上):建立计算机

创建虚拟硬件个几个小问题:

1、 loop0 无法正常使用如下命令查看空闲的,换一个即可

sudo losetup -f
复制代码


2、 grub-install 失败

Installing for x86_64-efi platform.grub-install: error: cannot find EFI directory.
复制代码

指定一下 --target=i386-pc


11 | 设置工作模式与环境(中):建造二级引导器


GRUB 头 imginithead.asm 中切换 32bits_mode,并没有设置 CR0.PE,而在第 15 讲引导 linux 时却在 go_to_protected_mode 函数中设置了 CR0.PE,一开始我以为是笔误,后来发现引导器也有很多规范:


  1. Cosmos 遵守的是 GRUB Multiboot Specification,所以在执行 imginithead.asm 时已经是 32 位保护模式;

  2. 而 linux 采用的是自己的规范,引导的时候是 16 位实模式。


详见:https://stackoverflow.com/questions/4821911/does-grub-switch-to-protected-mode


12 | 设置工作模式与环境(下):探查和收集信息整个引导流程:


  1. BIOS(0xffff0) ,将硬盘第一扇区加载到 0x7c00,也就是主引导器 GRUB

  2. GRUB 主引导(0x7c00) ,initldrimh.bin 按照 Multiboot Specification 被执行

  3. 将 initldrsve.bin 加载到 0x1000

  4. 将 initldrkrl.bin 加载到 0x200000

  5. 二级引导器 initldrkrl.bin(0x200000)

  6. 通过 initldrsve.bin 调 BIOS 中断获取机器信息,由于 BIOS 中断是实模式,而引导程序是保护模式,所以封装了 initldrsve.bin 处理模式的切换

  7. 将 Cosmos.bin 加载到 0x2000000

  8. 建立 MMU 页表

  9. 内核 Cosmos.bin(0x2000000),


13 | 第一个 C 函数:如何实现板级初始化?



发布于: 刚刚阅读数: 8
用户头像

袁世超

关注

还未添加个人签名 2017-11-30 加入

还未添加个人简介

评论

发布
暂无评论
《操作系统实战 45 讲》笔记1——引导部分_操作系统_袁世超_InfoQ写作社区