引导:boot实现

1、计算机启动流程

1)上电启动后,CPU处于16位运行的实模式,分页机制禁止,此时只有1MB内存可用,没有特权级

2)CPU跳转到BIOS的入口(地址为0xFFFF0)开始执行

3)BIOS进行硬件自检(POST),主板、硬盘、显卡、内存等自检,并保存一些配置数据到特定内存地址处(如硬盘的数量)

4)根据配置的启动顺序(光驱、U盘、硬盘等),加载引导代码运行。例如,从硬盘启动时,将硬盘的第1扇区(主引导纪录)读取到0x7c00处并且跳转到该地址处运行

5)引导代码对操作系统的运行初始环境进行配置,并加载操作系统到内存中

6)跳转到操作系统运行

主要工作是完成最后两步,实现自己的引导代码和操作系统

2、接管控制权

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

        1)编写引导程序,控制在512字节内,写入启动磁盘的第0扇区,

        2)在第0扇区的最后两个字节(1FE, 1FF)写入0x55, 0xaa。

BIOS在完成自检后,会检查第0扇区的最后两个字节是否是0x55, 0xaa,以此来判断是否包含有效的引导代码。如果是,则自动从引导磁盘的第0扇区加载引导程序到0x7c00处执行。具体如下图所示。

本项目只涉及到启动磁盘没有分区的情况,简化项目过程

3、创建可引导的启动程序

后续所有的开发工作都将在此工作模板基础之上完成。整个工程中包含了引导程序、加载程序、操作系统以及三个具体的应用进程。

整体的工作流程如下图所示: 

        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,开始正式的调试过程

4、初始化引导程序

本项目只采用二级引导,即boot只完成loader的加载工作、再由loader完成具体的初始化工作和内核加载

实模式:CPU启动后自动进入实模式,这种模式无任何保护机制,只能运行16位代码,不支持虚拟内存、不支持访问1MB以上的内存,后续可以通过代码切换到高级保护模式

1)内核寄存器:

通用寄存器:CPU进入实模式后,AX/EBX/ECX/EDX仅能使用低16位,即AX/BX/CX/DX。

段寄存器:CS/DS/SS/ES/FS/GS为段寄存器,用于指向一段内存区域。访问特定地址时,需要使用 段:偏移 的形式

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

2)存储映射

实模式模式只支持访问1MB以内的内存,其中可供我们自由使用的区域如下图灰色部分所示。具体的内存映射如下:

5、使用BIOS中断显示字符

BIOS中断:在0地址处,存储着中断向量表,在访问时通过软中断int xx来实现对特定功能的调用。具体每个功能的执行时需要的参数,通过寄存器传递。

显示字符:利用INT 10,AH=0xE显示字符

该BIOS中断的作用是:显示字符,同时光标前移,其中AL = 字符、BL = 前景色,BH=页码。具体来说:BH 为目前的显示页,如果是在图形模式,则 BH 须设为 0,假如是在图形模式下,也可以设定 BL 来表示文字的颜色,文字模式下的 BL 则无功能

6、使用BIOS中断读取磁盘

为简单起见, loader在磁盘上的位置位于紧接boot之后的扇区,即第1扇区开始。长度不限。(不是必须放在第一扇区0x8000处)

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

利用INT13中断读取磁盘

6、进入C语言环境并进入Loader

主要目的是创建loader工程,编写简单的代码,并实现从boot进入到loader中运行。实现两级跳转:从汇编到C,再从boot 到 loader。

1)从汇编语言跳到C语言

从汇编跳转到C语言执行,有两种方式:一是用JMP直接跳转过去;二是用CALL指令进行函数调用。由于是从boot中的汇编跳转到C语言,无需返回,所以直接用JMP跳转。

在使用前,先用.extern boot_entry导入外部boot_entry符号,然后再用jmp boot_entry跳转(具体看代码)

2)跳转到指定loader运行

boot和loader分属两个工程,共生成两个bin文件。从boot跳到loader,只知道loader的起始地址为0x8000,所以采用函数指针转换

7、总结与代码分析

1)代码结构

boot和loader下都有对应的汇编文件。

2)boot中的重要代码

start.S和boot.c

#include "boot.h"

  	// 16位代码,务必加上
  	.code16
 	.text
	.global _start
	.extern boot_entry
_start:
	// 重置数据段寄存器
	mov $0, %ax
	mov %ax, %ds
	mov %ax, %ss
	mov %ax, %es
	mov %ax, %fs
	mov %ax, %gs

	// 根据https://wiki.osdev.org/Memory_Map_(x86)
	// 使用0x7c00之前的空间作栈,大约有30KB的RAM,足够boot和loader使用
	mov $_start, %esp

	// 显示boot加载完成提示
	mov $0xe, %ah
	mov $'L', %al
	int $0x10

	// 加载loader,只支持磁盘1
	// https://wiki.osdev.org/Disk_access_using_the_BIOS_(INT_13h)
read_loader:
	mov $0x8000, %bx	// 读取到的内存地址
	mov $0x2, %cx		// ch:磁道号,cl起始扇区号
	mov $0x2, %ah		// ah: 0x2读磁盘命令
	mov $64, %al		// al: 读取的扇区数量, 必须小于128,暂设置成32KB
	mov $0x0080, %dx	// dh: 磁头号,dl驱动器号0x80(磁盘1)
	int $0x13
	jc read_loader

	// 跳转至c部分执行,再由c部分做一些处理
	jmp boot_entry

	// 原地跳转
	jmp .

	// 引导结束段
	.section boot_end, "ax"
boot_sig: .byte 0x55, 0xaa
__asm__(".code16gcc");   //告诉编译器采用16位模式(实模式)

#include "boot.h"

#define	LOADER_START_ADDR	0x8000		// loader加载的地址

/**
 * Boot的C入口函数
 * 只完成一项功能,即从磁盘找到loader文件然后加载到内容中,并跳转过去
 */
void boot_entry(void) {
	((void (*)(void))LOADER_START_ADDR)();
} 

3)loader中的重要代码

 	// 16位代码,务必加上
  	.code16
 	.text
 	.extern loader_entry
	.global _start
_start:
	// 栈和段等沿用之前的设置,也可以重新设置
	// 这里简单起见,就不做任何设置了
	// 你可能会想,直接跳到loader_entry,但这样需要先知识loader_entry在哪儿
	// boot没有这个能力做到,所以直接用汇编,以便_start处于整个bin文件开头,这样boot直接跳到开头就可以
	jmp loader_entry

// 16位代码,必须加上放在开头,以便有些io指令生成为32位
__asm__(".code16gcc");

void loader_entry(void) {
    for(;;) {}
}

4)总结

了解计算机启动流程?

启动—跳转到BIOS—自检—加载引导代码—引导代码加载操作系统到内核—跳转到操作系统执行
BIOS自检完成会检查第0扇区最后两个字节是够是x55, 0xaa,来确认是否包含有效的引导代码
 

  • 15
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值