引导程序:接管控制权
首先工作在16位实模式, 分页机制保护模式被禁止, 只可以使用1MB的内存, 之后会跳转到BIOS, 入口地址是0xffff:0x0000, BIOS这时候会进行自检, 检查硬盘,显卡,内存等,并保存一些参数,之后会加载引导程序到内存中, 运行引导代码, 之后按照引导代码进入操作系统
首先BIOS会加载磁盘的第一个扇区
**引导程序: **
- 硬件检测: 检测内存容量, 检测硬盘的数量
- 进入保护模式: 16位实模式到32位保护模式, 实现4G内存的访问
- 加载操作系统
因为只加载了512字节, 内存比较小, 所以难以完成
- 把部分文件放在操作系统里面
- 使用二级加载, 首先加载一个loader, 负责完成其他工作, 之后再由loader加载操作系统
- 实模式: 是为了兼容比较老的CPU, 只能使用16位, 没有页操作等保护模式内容, 只可以使用1MB的内存
使用的寄存器
在实模式的时候只能使用16位的寄存器, 8086 CPU 中寄存器总共为 14 个,且均为 16 位 。即 AX,BX,CX,DX,SP,BP,SI,DI,IP,FLAG,CS,DS,SS,ES
缩写 | 含义 |
---|---|
AX,BX,CX,DX称作为数据寄存器 | AX (Accumulator):累加寄存器,也称之为累加器;BX (Base):基地址寄存器;CX (Count):计数器寄存器;DX (Data):数据寄存器; |
SP 和 BP又称作为指针寄存器 | SP (Stack Pointer):堆栈指针寄存器;BP (Base Pointer):基指针寄存器; |
SI 和 DI又称作为变址寄存器 | SI (Source Index):源变址寄存器;DI (Destination Index):目的变址寄存器; |
控制寄存器
IP (Instruction Pointer):指令指针寄存器;
FLAG:标志寄存器;
段寄存器
CS (Code Segment):代码段寄存器;
DS (Data Segment):数据段寄存器;
SS (Stack Segment):堆栈段寄存器;
ES (Extra Segment):附加段寄存器;
程序的加载
有两种硬盘, 机械硬盘以及固态硬盘
机械硬盘有几个部分, 扇区=>磁道=>盘面, 不同盘面的相同磁道叫做柱面, 使用磁头进行读取
固态硬盘使用扇区进行存储, 从零开始每一个块是512字节
- BIOS如何知道磁盘第一扇区是引导代码
start qemu-system-i386 -m 128M -s -S -drive file=disk.img,index=0,media=disk,format=raw
如果去掉两个-s -S qemu就不会与VSCode进行连接
在启动的时候会检查磁盘第一扇区最后的两个字节, 应该是0x55 AA, 否则不会进行引导
程序分布
- .vscode
-
- launch.json调试的时候使用的文件
- settings.json vsCode配置文件
- tasks.json运行任务的时候会执行的任务配置
- build
- script
- source
-
- applib系统调用库
- boot引导程序
- comm
- init 初始进程
- kernel操作系统内核
- loader加载程序
- loop应用程序
- shell命令行应用
- applib系统调用库
编译整个工程会生成elf文件, 有一部分写入磁盘里面, 还有一部分转换之后再写入
引导程序的作用
检测硬件, 进入保护模式, 加载操作系统
引导程序使用BIOS打印字符
调用INT软中断
BIOS生成了一个中断向量表, 其中一部分被BIOS使用, 通过寄存器传递数据
AH=0xE, 显示字符
AL=字符, BL=前景色, BH=页码, 图形模式下BH必须为0, 图形模式模式下BL设置文字的颜色, 文字模式下不行
AH=00H AH=00/INT 10H 是用来设定显示模式的服务程序,AL 寄存器表示欲设定的模式 AH=01H 可以把光标想成一个小的矩形,平时这个矩形扁平位于某字底部,但藉由此功能可以改变其大小与位置。光标起始处与终止处分别由 CL 与 CH 的 0 到 4 位表示 AH=02H 此功能是设定光标位置,位置用 DH、DL 表示,DH 表示列号,DL 表示行号。由左至右称之为『列』,屏幕最上面一列为第零列,紧靠第零列的下一列称为第一列……;由上而下称之为『行』,屏幕最左边一行称之为第零行,紧靠第零行右边的一行为第一行。故最左边,最上面的位置为 DH=0 且 DL=0;最左边第二列,DH=1,DL=0。如果是文字模式时,BH 为欲改变光标位置的显示页,如果是图形模式,BH 要设为 0。
以行列来说明 DH、DL 之意义,小木偶常常搞混,底下以座标方式解释。在文字模式下,字符的位置类似数学直角座标系的座标,但是 Y 轴方向相反,Y 轴是以屏幕最上面为零,越下面越大,直到 24 为止,存于 DH 内。X 轴和直角座标系相同,越右边越大,存于 DL 内,其最大值视显示模式而变。 AH=03H AH=03H/INT 10H 这个中断服务程序返回时,会在 DX 里面有光标的行列位置,CX 内有光标的大小,DX、CX 之数值所代表的意义和 AH=02H/INT 10H、AH=01H/INT 10H 相同。 AH=04H 此功能是探测光笔之位置,似乎只有 CGA 卡有接上光笔 AH=05H 这个功能是把指定的显示页显示于屏幕上,欲显示的显示页于 AL 寄存器中指定。此功能只能在文字模式下才能发生作用。 AH=06H/07H 这个服务程序的功用是把某一个设定好的矩形区域内的文字向上或向下移动。先说明向上移动,即调用 AH=06H/INT 10H。当此服务程序工作时,会使矩形区域的文字向上移动,而矩形区域底端移进空格列。向上移动的列数存入 AL 中 ( 如果 AL 为零,表示使矩形区域的所有列均向上移 ),底端移入空格列的属性存于 BH,矩形区域是藉由 CX、DX 来设定左上角与右上角的座标,左上角的行与列分别由 CL、CH 设定,右下角的行与列由 DL、DH 设定。
AH=07H/INT 10H 和 AH=06H/INT 10H 相似,只是卷动方像不同而已。
AH=08H 这个服务程序是用来取得光标所在位置的字符及属性,调用前,BH 表示欲读取之显示页,返回时,AL 为该位置之 ASCII 字符,AH 为其属性。 AH=09H 这个功能是在光标位置显示字符,所要显示字符的 ASCII 码存于 AL 寄存器,字符重复次数存于 CX 寄存器,显示页存于 BH 寄存器,属性存于 BL 寄存器,其属性使用与 AH=08/INT 10H 一样。
AH=0AH 这个功能和 AH=09H/INT 10H 一样,差别在 AH=0AH 只能写入一个字符,而且不能改变字符属性。
AH=0BH 这个服务程序是选择调色盘。显示模式 5 是 320*200 的图形模式,最多可以显示 4 种颜色,这四种颜色的意思是最多可以『同时』显示一种背景色及三种前景色,而这三种前景色有两种方式可供选择,因此事实上,在显示模式 5 有两种调色盘可供选择。就好像您去买 12 种颜色的水彩,但可在调色盘上以任意比例搭配出许多种颜色。
调色盘 0 的三色是绿、红、黄;调色盘 1 的三色是青、紫红、白。背景色有 16 六种可供选择,这 16 种就是注一的 16 色。调用此中断时,先决定要设定背景色抑或调色盘,
要设定背景色时,则使 BH 为 0,再使 BL 之数值为 0 到 0fh 之间表示注一的 16 色之一。 要设定调色盘时,则使 BH 为 1。再设定 BL 为零或一表示选择那一种调色盘。 背景色只有在前景色为 0 时才会显现出来。
AH=0CH AH=0Ch/INT 10H 是在绘图模式中显示一点 ( 也就是写入点像,write graphics pixel ),而 AH=0DH/INT 10H 则是读取点像 ( read graphics pixel )。
写入时,要写入位置 X 座标存于 CX 寄存器,Y 座标存于 DX 寄存器,颜色存于 AL 寄存器。和文字模式相同,萤光幕上的 Y 座标是最上面一列为零,越下面越大,X 座标则和数学的定义相同。CX、DX、AL 值之范围与显示模式有关. AH=0DH/INT 10H 则是读取某一位置之点像,您必须指定 CX、DX,而 INT 10H 会传回该位置点像之颜色。
AH=0EH 这个子程序是使显示器像打字机一样的显示字符来,在前面用 AH=09H/INT 10H 和 AH=0AH/INT 10H 都可以在萤光幕上显示字符,但是这两奘方式显示字符之后,光标位置并不移动,而 AH=0EH/INT 10H 则会使光标位置移动,每显示一个字符,光标会往右移一格,假如已经到最右边了,则光标会移到最左边并移到下一列,假如已经移到最下面一列的最右边,则屏幕会向上卷动。
AL 寄存器存要显示的字符,BH 为目前的显示页,如果是在图形模式,则 BH 须设为 0,假如是在图形模式下,也可以设定 BL 来表示文字的颜色,文字模式下的 BL 则无功能。
AH=0FH 这个服务程序是得到目前的显示模式,调用前只需使 AH 设为 0fh,当由 INT 10H 返回时,显示模式存于 AL 寄存器 ( 参考 AH=00H/INT 10H 的显示模式表 ),目前的显示页存于 BH 寄存器,总字符行数存于 AH 寄存器。
#include "boot.h"
// 16位代码,务必加上
.code16
.text
.global _start
_start:
mov $0, %ax
mov %ax, %ds
mov %ax, %ss
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov $_start, %esp
mov $0xe, %ah
mov $'L', %al
int $0x10
jmp .
// 引导结束段
.section boot_end, "ax"
//下面这一段的地址使用的是在编译的时候指定的标识符
boot_sig: .byte 0x55, 0xaa
# 使用自定义的链接器
set(CMAKE_EXE_LINKER_FLAGS "-m elf_i386 -Ttext=0x7c00 --section-start boot_end=0x7dfe")
set(CMAKE_C_LINK_EXECUTABLE "${LINKER_TOOL} <OBJECTS> ${CMAKE_EXE_LINKER_FLAGS} -o ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.elf")
加载磁盘中的loader
这里设计为加载到0x8000这个位置
在这里使用的INT 13
AH = 02H
AL = 扇区数
CH = 柱面cx = ch:cl
CL = 扇区
DH = 磁头
DL = 驱动器, 00H - 7FH, 软盘 80H-0FFH:硬盘
ES:BX = 缓冲区地址, 之前已经把ES设置为0了
出口参数: CF = 0, 操作成功, AH = 00H, AL = 传输的扇区数量, 否则AH = 状态码
查看磁盘文件是否成功加载
使用命令x可以进行查看内存
命令的格式是 x / <n/f/u>
- n是一个正整数, 表示显示的长度
- f表示显示的格式
- x十六进制
- d十进制
- u按照十六进制显示无符号
- o按照八进制显示
- t按照二进制显示
- a按照十六进制显示
- c按照字符格式
- f按照浮点数格式
- u显示的单元大小, 默认是4bytes, b表示单字节, h双字节, w四字节, g八字节
read_loader:
//下边是读取磁盘的代码
//设置起始的地址
mov $0x8000, %bx
mov $0x2, %ah
//BIOS是从1开始编号, 设置为第二扇区开始
mov $0x2, %cx
mov $64, %al
mov $0x0080, %dx
int $0x13
jc read_loader
jmp .
// 引导结束段
.section boot_end, "ax"
跳转到bootloader的C函数
这个函数不再返回
修改编译文件
if not exist "disk1.vhd" (
echo "disk1.vhd not found in image directory"
notepad win_error.txt
exit -1
)
if not exist "disk2.vhd" (
echo "disk2.vhd not found in image directory"
notepad win_error.txt
exit -1
)
set DISK1_NAME=disk1.vhd
dd if=boot.bin of=%DISK1_NAME% bs=512 conv=notrunc count=1
dd if=loader.bin of=%DISK1_NAME% bs=512 conv=notrunc seek=1
dd: 命令行工具,用于复制和转换文件
if=boot.bin: 指定输入文件为 boot.bin
of=%DISK1_NAME%: 指定输出文件为 %DISK1_NAME%,%DISK1_NAME% 是一个环境变量,表示磁盘的名称
bs=512: 指定每次读取和写入的块大小为 512 字节
conv=notrunc: 指定不截断输出文件,即保留输出文件中原有的数据
count=1: 指定只复制一个块,即 512 字节
第二条命令:
dd: 命令行工具,用于复制和转换文件
if=loader.bin: 指定输入文件为 loader.bin
of=%DISK1_NAME%: 指定输出文件为 %DISK1_NAME%,%DISK1_NAME% 是一个环境变量,表示磁盘的名称
bs=512: 指定每次读取和写入的块大小为 512 字节
conv=notrunc: 指定不截断输出文件,即保留输出文件中原有的数据
seek=1: 指定从输出文件的第二个块开始写入,即跳过第一个块
在调试的时候需要加上loader.elf文件的描述符
project(loader LANGUAGES C)
# 使用自定义的链接器
set(CMAKE_EXE_LINKER_FLAGS "-m elf_i386 -Ttext=0x8000")
set(CMAKE_C_LINK_EXECUTABLE "${LINKER_TOOL} <OBJECTS> ${CMAKE_EXE_LINKER_FLAGS} -o ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.elf")
# 将所有的汇编、C文件加入工程
# 注意保证start.asm在最前头,这样对应的程序会放在bin文件开头,从而在加载到0x7c00时能在开头处
file(GLOB C_LIST "*.c" "*.h")
add_executable(${PROJECT_NAME} start.S ${C_LIST})
# bin文件生成,写入到image目录下
add_custom_command(TARGET ${PROJECT_NAME}
POST_BUILD
COMMAND ${OBJCOPY_TOOL} -O binary ${PROJECT_NAME}.elf ${CMAKE_SOURCE_DIR}/../../image/${PROJECT_NAME}.bin
COMMAND ${OBJDUMP_TOOL} -x -d -S -m i8086 ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.elf > ${PROJECT_NAME}_dis.txt
COMMAND ${READELF_TOOL} -a ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.elf > ${PROJECT_NAME}_elf.txt
)
对文件进行编译