boot的实现

计算机启动流程

  1. 上电启动后,CPU处于16位运行的实模式,分页机制禁止,此时只有1MB内存可用,没有特权级
  2. CPU跳转到BIOS的入口(地址为0xFFFF0)开始执行
  3. BIOS进行硬件自检(POST),主板、硬盘、显卡、内存等自检,并保存一些配置数据到特定内存地址处(如硬盘的数量)
  4. 根据配置的启动顺序(光驱、U盘、硬盘等),加载引导代码运行。例如,从硬盘启动时,将硬盘的第1扇区(主引导纪录)读取到0x7c00处并且跳转到该地址处运行
  5. 引导代码对操作系统的运行初始环境进行配置,并加载操作系统到内存中
  6. 跳转到操作系统运行

接管运行控制权

要接管系统的控制权,需要完成两步操作:

  1. 编写引导程序,控制在512字节内,写入启动磁盘的第0扇区,
  2. 在第0扇区的最后两个字节(1FE, 1FF)写入0x55, 0xaa。
    BIOS在完成自检后,会检查第0扇区的最后两个字节是否是0x55, 0xaa,以此来判断是否包含有效的引导代码。如果是,则自动从引导磁盘的第0扇区加载引导程序到0x7c00处执行。

创建可引导的启动程序

  1. 在VIsual Studio Code中编写C源文件、汇编文件、链接脚本
  2. CMake根据配置脚本,调用GCC对源文件进行编译和汇编,调用LD进行链接生成可执行的ELF文件
  3. CMake还会调用OBJCOPY将ELF文件进行缩小,或者转换成BIN文件。
  4. 调试前,Visual Studio Code调用一些小工具将ELF、BIN文件写入磁盘映像
  5. 调试时,QEMU加载磁盘映像文件,然后等待GDB连接。
  6. GDB连接上QEMU,开始正式的调试过程。

初始化引导程序

引导程序的作用

引导程序的主要作用:boot只完成loader的加载工作,再由loader完成具体的初始化工作和内核加载。

实模式

CPU启动后,自动进入所谓的实模式。可以理解其为最早期的8086芯片的工作模式。这种模式无任何保护机制,只能运行16位代码、不支持虚拟内存、不支持访问1MB以上的内存。后续为通过代码切换到现代的高级保护模式。

内核寄存器

x86包含很多寄存器,以下仅列举与我们编程有关的部分。其中在CPU启动进入实模式后,EAX/EBX/ECX/EDX仅能使用低16位,即AX/BX/CX/DX。

CS/DS/SS/ES/FS/GS为段寄存器,用于指向一段内存区域。访问特定地址时,需要使用段:偏移的形式,生成的地址为段值 << 4 + 偏移,例如访问0x7c00,需要使用0x7c0: 0,或者0: 0x7c00。

在本课程中,没有使用其复杂的分段模式,即采用平坦模式,所有的段寄存器全部指向0。

存储映射

实模式模式只支持访问1MB以内的内存,BIOS会自动将磁盘的第0扇区加载到0x7c00地址处。

使用BIOS中断显示字符

BIOS中断

BIOS提供了一些服务函数,方便开发操作系统使用。
在使用时并不需要知道特定函数的入口地址,因其内部通过向量表的方式去访问,向量表里保存了函数的入口地址。在0地址处,存储着中断向量表,在访问时通过软中断int xx来实现对特定功能的调用。具体每个功能的执行时需要的参数,通过寄存器传递。
显示字符串有很多种方法,这节课时只使用一种简单的方式。
该BIOS中断的作用是:显示字符,同时光标前移,其中AL = 字符、BL = 前景色,BH=页码。
具体来说,BH 为目前的显示页,如果是在图形模式,则 BH 须设为 0,假如是在图形模式下,也可以设定 BL 来表示文字的颜色,文字模式下的 BL 则无功能。至于显示页是什么,不需要了解,我们只需要将它设置成零即可。
在这里插入图片描述

使用BIOS中断读取磁盘

存储规划

由于boot的容量限制,所以将大部分初始化和加载内核的功能放在loader中。为简单起见, loader在磁盘上的位置位于紧接boot之后的扇区,即第1扇区开始。长度不限。

boot启动之后,将调用BIOS中断从第1扇区加载loader到0x8000地址处,之后跳转到0x8000地址处运行。

是否必须放在第1扇区,0x8000地址处?不是必须的。可以有其它选择。本课程只是从简单、方便地角度去考虑,并不考虑是否节省内存。

INT13磁盘读取

BIOS提供了磁盘操作的服务中断,其具体使用方法如下:

●AH=02H

●AL=扇区数

●CH=柱面 cx = ch: cl

●CL=扇区

●DH=磁头

●DL=驱动器,00H7FH:软盘;80H0FFH:硬盘

●ES:BX=缓冲区的地址

●出口参数:CF=0——操作成功,AH=00H,AL=传输的扇区数,否则,AH=状态代码,参见功能号01H中的说明。CF标志见EFLAS寄存器中的CF位

注:磁盘还支持LBA模式下的读取,但是这种读取方式相比直接用BIOS会复杂一些,代码量也多,不易用汇编实现。所以,此处使用BIOS中断读取;在后续的loader实现中,将使用LBA读取,到时可以将其与INT 13读取方式进行比较。

GDB查看内存命令

如果要使用GDB查看内存内容,可以使用x命令,其指令格式如下:

例如 x /100u 0x10000

●n 是一个正整数,表示显示内存的长度。

●f 表示显示的格式

○x 按十六进制格式显示变量。

○d 按十进制格式显示变量。

○u 按十六进制格式显示无符号整型。

○o 按八进制格式显示变量。

○t 按二进制格式显示变量。

○a 按十六进制格式显示变量。

○c 按字符格式显示变量。

○f 按浮点数格式显示变量。

●u:显示的单元大小。默认是4个bytes。b表示单字节,h表示双字节,w表示四字节,g表示八字节。

进入C语言环境并跳到loader

从汇编语言跳到C语言

从汇编跳转到C语言执行,有两种方式:一是用JMP直接跳转过去;二是用CALL指令进行函数调用。
在本课时中,由于是从boot中的汇编跳转到C语言,无需返回,所以直接用JMP跳转。
在使用前,先用.extern boot_entry导入外部boot_entry符号,然后再用jmp boot_entry跳转。

跳转到指定loader运行

boot和loader分属两个工程,共生成两个bin文件。
从boot跳到loader,只知道loader的起始地址为0x8000,所以采用函数指针转换。(void ()(void) 为无参数、无返回值的函数类型。((void ()(void))LOADER_START_ADDR)() 即认为在0x8000地址处存放了这种类型的函数的代码,通过调用函数函数进入到loader中运行。
注:无论是boot还是loader,其工程均已经配置好让start.S中的代码位于生成的bin文件开头。所以无论是boot还是loader,其最开头的指令总是程序的入口指令。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值