内容为 李治军老师操作系统课 的部分笔记
计算机开机执行的操作
以X86 PC为例(X86为Intel处理器架构,即指令集架构(ISA Instruction Set Architecture))
一个地址由段寄存器CS和段内偏移IP组成
-
x86 PC 刚开机时,CPU初始状态为实模式。
-
此时CPU设置CS=0xFFFF,IP=0x0000(CS为Code Segment Register 代码段寄存器,确定起始内存段)
物理地址 = (CS<<4)+ IP = 0xFFFF0 //所以这就是第一条内存指令
-
寻址0xFFFF0(ROM BIOS映射区,为BIOS 启动入口地址)
-
执行0xFFFF0的指令,检查RAM、键盘、显示器、软硬磁盘
-
BIOS将磁盘0磁道0扇区(引导扇区BootSector)读入0x7c00处(一个扇区为512B)
-
设置CS=0x07c0,IP=0x0000(此时计算出的物理地址为0x7c00,寻址0x7c00,执行引导扇区的指令)
bootsect.s
引导扇区存放的代码 bootsect.s(s后缀为汇编代码),该代码来将操作系统加载到内存中
; 在 as86 汇编语言程序中,凡是以感叹号’!’或分号’;’开始的语句均为注释
;1、操作为:bootsect 把自己搬运到 0x90000,并跳转
;BOOTSEG=0x07c0 INITSEG=0x9000 SETUPSEG=0x9020
entry _start ; 告知链接程序,程序从 start 标号开始执行
_start:
mov ax,#BOOTSEG
mov ds,ax ! ds = 0x07c0
mov ax,#INITSEG
mov es,ax ! es = 0x9000
mov cx,#256 ! cx为计数器,搬运 256 次
!ds为数据段,es为附加段,都为段寄存器,段寄存器需要有偏移才构成实际地址
sub si,si ! sub为减,前减后结果给前,si-si=0,si = 0
sub di,di ! di = 0
!si为源偏移,di为目标偏移
! ds:si = 0x07c0:0x0,实际地址:ds<<4+si=0x7c00
! es:di = 0x9000:0x0 实际地址:es<<4+di=0x90000
rep movw ! 每次搬运1个字,即 2 字节,搬运256次,即512字节,为一个扇区大小
!将7c00处的256个字移动到90000中,7c00为bootsect.s存放的地址,此时将其移动到90000
!start位置为90000
jmpi go,INITSEG !jmpi段内转跳,cs=INITSEG ip=go
! INITSEG<<4+go,假设go为100,则此时地址为90100
!start为90000,start的模块为100,start进行完后正好到90100也就是go的位置
————————————————
;2、转跳到go执行下面指令,设置段寄存器和栈
go: mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax ; cs = ds = es = ss = 0x9000,
!ss为栈段,sp为栈顶指针,栈顶地址为ss:sp,即ss<<4+sp,实模式下栈是往下长的
mov sp,#0xFF00 !ss:sp = 0x9000:0xff00 ,栈的设置 0x9ff00
! 因为从 0x90200 地址开始处还要放置 setup 程序,setup 大约为 4 个扇区
! 0x200 * 5 = 0xA00 (512十六进制下为200),0x90200+0xA00=0x90C00
!所以setup 占用范围 = 0x90200 ~ 0x90C00
!又因为栈顶地址必须高于 setup 程序的最高地址(0x90C00)
!栈顶地址(0x9FF00) - setup 结束地址(0x90C00) = 0xF500,栈的大小为0xF500
————————————————
;3、加载 setup 模块到 0x90200
load_setup:
mov dx,#0x0000 ! dh磁头号为0,dl驱动器号为0
mov cx,#0x0002 ! cl起始扇区号2,因为bootset占了第一个扇区,故从第二个扇区读 ch柱面号0
mov bx,#0x0200 ! 偏移地址0x200
mov ax,#0x0200+SETUPLEN ! 功能号ah=0x02,表示读取扇区,al=要读的扇区数目=SETUPLEN=4
!以上为从第二个扇区开始,读取4个扇区,因为第一个扇区地址为90000,一个扇区512字节,
十六进制为0x200,故setup起始地址为90200
!es:bx=内存地址,在执行go模块后,es=0x9000,bx=200,es:bx正好也为90200
int 0x13 ! BOIS读磁盘的中断
jnc ok_load_setup ! CF = 0 则跳转
mov dx,#0x0000
mov ax,#0x0000 !ah功能号为0,al扇区数量为0
int 0x13 ! 复位磁盘
j load_setup
————————————————
ok_load_setup:
;4、获得磁盘驱动器参数(主要是每磁道的扇区数)
mov dl,#0x00 !驱动器号为0,说明是软盘
mov ax,#0x0800 !ah功能号为0x08,为获取驱动器参数
int 0x13
mov ch,#0x00 !这里用不上软盘的最大磁道号,可以使CH=0
mov sectors,cx !存下磁道的扇区数
;5、打印一段开机logo
mov ah,#0x03 !功能号,读光标的位置
xor bh,bh !bh=页号=0
int 0x10 !ah为03,故读光标
mov cx,#24 ! 24个字符
mov bx,#0x0007 ! bh页号为0, bl属性为07 , 黑底浅灰字
mov bp,#msg1 !bp为显示的字符在内存的什么地方,#msg1为偏移,在bootsect.s尾部
mov ax,#0x1301 !功能号ah为13,为在屏幕上显示字符串
!功能号子参数为01,为使用指定属性,光标更新
int 0x10 !ah为13,故为显示字符,同一中断号调用不同功能
;6、加载 system 模块
mov ax,#SYSSEG ! SYSSEG=0x1000
mov es,ax ! segment of 0x010000
call read_it !读入system模块
;7、跳转到 setup.s 执行
jmpi 0,SETUPSEG //转入0x9020:0x0000,即90200执行setup.s
!bootsect.s文件尾部:
sectors: .word 0 !扇区磁道数
msg1:
.byte 13,10
.ascii "Loading system ..." !开机时屏幕显示的logo,共cx个字符
.byte 13,10,13,10
!以上操作为在内存为bp的地方,读取24个字符,页号为0,颜色为黑底浅灰色字
总结,以上代码作用为: 把 bootsect 自己从0x7c00 拷贝到0x90000,设置段寄存器和栈,然后从磁盘加载 setup 模块到0x90200,再打印一段提示信息,最后加载 system 模块,并跳转到 setup 的入口执行。
setup.s
操作系统的代码,完成启动前的操作
INITSEG = 0x9000 ! bootsect.s 的段地址
SYSSEG = 0x1000 ! 系统加载在 0x10000
SETUPSEG = 0x9020 ! 本程序的段地址
start: mov ax,#INITSEG
mov ds,ax ;ds=0x9000
mov ah,#0x03 ;0x10中断下ah=0x03为 读取光标位置返回给dx
xor bh,bh int 0x10
dx mov [0],dx ;即mov WORD PTR [ds:0], dx 从上可知ds为9000
[0]前面默认有个段寄存器,当前段都指向9000,9000<<4 +0=0x90000
;故将dx给0x90000
mov ah,#0x88 ;0x15中断下,ah=0x88为获取扩展内存大小,返回给ax
int 0x15
mov [2],ax ... ;[2]为9000<<4+2=0x90002,将扩展内存数存在0x90002处
cli //不允许中断
mov ax,#0x0000 cld
do_move: mov es,ax ;es为0x0000 ,do_move类似do while循环,此处进入循环
add ax,#0x1000 ;ax为0x1000
cmp ax,#0x9000 ;ax-9000!=0,故zf=0
jz end_move ;因zf=0,所以不相等,不跳转,直到ax==0x9000,此时zf=1,终止循环
;end_move为循环终止
mov ds,ax ;ds=0x1000
sub di,di
sub si,si ;di=si=0x0000
;此时ds:si源地址0x10000 es:di目标地址0x00000
mov cx,#0x8000 ;cx为计数器
rep
movsw ;从0x10000到0x00000移动字,搬运32768次,每次搬运1字,即2字节,2*32768=65536字节
即移动0x10000字节,故是0x10000~0x20000移动到0x00000~0x10000
;最后一次循环es:di为0x70000,di:si为0x80000,故是0x80000~0x90000移动到0x70000~0x80000
jmp do_move ;跳转do_move继续循环
;以上循环结果为,0x10000~0x90000内容搬运到0x00000~0x80000
;以上结果为操作系统往后一直处于0地址开头,并获取硬件参数,为操作系统管理硬件做准备