说明:记录的大部分是我掌握不熟悉的内容,所以知识点不是那么全面。
按下BIOS的电源键后,便进入了实模式,首先运行的第一个程序是BIOS。BIOS代码的作用:检测、初始化硬件、建立中断向量表、加载MBR等。BIOS被写进ROM里面,0xF0000-0xFFFFF。开机一瞬间,CPU的 cs:ip 被初始化为0xF000:0xFFF0,段基址乘以16即左移4位,得到0xFFFF0,此地址是BIOS的入口地址,此地址离1M顶端只有16B,所以需要再次跳转,在0xFFFF处有代码 jmp far f000:e05b,跳转到0xfe05b地址处。然后开始检测内存、显卡等外设信息,然后初始化硬件,开始在0x000-0x3FF处建立中断向量表,并填写中断向量信息,然后加载MBR。长度为1024B,可以容纳256个中断向量。
BIOS如何检测和初始化硬件:刚上面说过了,显卡、键盘、硬盘等这些外设或它的控制器(IO接口)有自己的处理器、内存(ROM和RAM)和寄存器,这些外设出厂时内存的ROM中就写入了自我检测程序、初始化程序、硬件操作访问程序,BIOS找到这些程序的地址直接调用即可。BIOS中建立的中断向量表对应的中断服务程序也是在这些硬件出厂时候就写入ROM的,这些中断程序的主要功能提供了硬件的访问方法,本质就是给出这些硬件的IO端口提供值,用 int+中断号调用后,在cpu的寄存器中提供参数,然后硬件ROM里面的中断程序返回一个结果。如显卡,要在屏幕上面显示时,int 0x10,便开始在中断向量表中找到了显存中对应的程序的入口地址,提供了参数后,CPU执行即可。
BIOS的最后一项工作便是校验启动盘0盘0道1扇区的内容(001),当发现这个扇区(512B)最后两个字节是0x55和0xaa时候,BIOS便认定这是主引导程序MBR,便开始启动BISO的加载程序将001扇区的内容加载到内存0x07c00处,然后执行跳转指令:jmp 0:0x7c00,cs由0xf000变成了0x0000。(小段字节序 0xaa55)
cpu只和IO接口进行数据交换,从来不和外设接触, io接口是cpu与外部设备的逻辑控制部件,起到桥梁的作用。显卡是显示器和cpu的IO接口,声卡是音响和cpu的IO接口,键盘鼠标硬盘都有IO接口。IO接口功能有:1,设置数据缓冲,解决cpu与外设的熟读不匹配问题。cpu处理数据速度很快,但是外设很慢,就可以先把数据存到IO接口的缓冲中,等需要时候传输过去。2,设置信号电平的转换。CPU的信号是TTL,外设一般为电机设备,需要转换信号。3,数据格式转换,数字信号经过D/A转换成模拟信号发送到外设。等等功能。
cpu通过总线来访问到IO接口。同一时刻,cpu只能和一个IO接口通信。同一时刻有很多请求时候cpu要考虑需要访问哪个IO接口,所以要仲裁模块--I/O control hub(输入输出控制中心--南桥).IO接口中都有一组端口(寄存器),cpu通过操作这些端口来控制访问硬件。有些端口独立编制,从0开始,同一个IO接口的端口号连续。cpu通过in/out指令和IO接口中的端口号通信。
;主引导程序
;------------------------------------------------------------
%include "boot.inc"
SECTION MBR vstart=0x7c00
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00
mov ax,0xb800
mov gs,ax
; 输出字符串:MBR
mov byte [gs:0x00],'M'
mov byte [gs:0x01],0xA4
mov eax,LOADER_START_SECTOR ; 起始扇区lba地址:0x2
mov bx,LOADER_BASE_ADDR ; 写入的地址:0x900
mov cx,1 ; 待读入的扇区数:1个扇区
call rd_disk_m_16 ; 以下读取程序的起始部分(一个扇区)
jmp LOADER_BASE_ADDR ;跳转到0x900处执行程序
;功能:读取硬盘n个扇区,将此定义为一个全局变量
rd_disk_m_16:
mov esi,eax ;备份eax,并且16位实模式下的扩展寻址方式,机器码加上反转指令0x67
mov di,cx ;备份cx
;读写硬盘:
;第1步:设置要读取的扇区数
mov dx,0x1f2
mov al,cl
out dx,al ;读取的扇区数
mov eax,esi ;恢复ax
;第2步:将LBA地址存入0x1f3 ~ 0x1f6
;LBA地址7~0位写入端口0x1f3
mov dx,0x1f3
out dx,al
;LBA地址15~8位写入端口0x1f4
mov cl,8
shr eax,cl
mov dx,0x1f4
out dx,al
;LBA地址23~16位写入端口0x1f5
shr eax,cl
mov dx,0x1f5
out dx,al
shr eax,cl
and al,0x0f ;lba第24~27位
or al,0xe0 ; 设置7~4位为1110,表示lba模式
mov dx,0x1f6
out dx,al
;第3步:向0x1f7端口写入读命令,0x20
mov dx,0x1f7
mov al,0x20
out dx,al
;第4步:检测硬盘状态
.not_ready:
;同一端口,写时表示写入命令字,读时表示读入硬盘状态
nop ;空操作,相当于sleep一下
in al,dx
and al,0x88 ;第4位为1表示硬盘控制器已准备好数据传输,第7位为1表示硬盘忙
cmp al,0x08
jnz .not_ready ;al与0x08不相等,则表示未准备好,继续等。
;第5步:从0x1f0端口读数据
mov ax, di
mov dx, 256
mul dx
mov cx, ax ; di为要读取的扇区数,一个扇区有512字节,每次读入一个字,
; 共需di*512/2次,所以di*256 cx为循环次数,每次循环会减一
mov dx, 0x1f0
.go_on_read: ;反复读取即可
in ax,dx
mov [bx],ax
add bx,2
loop .go_on_read
ret
times 510-($-$$) db 0
db 0x55,0xaa
;------------- loader和kernel ----------
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR ;作用是:编址时候将标号的地址加上这个地址开始编起,而不是说将程序加载到这个地址
; 输出背景色绿色,前景色红色,并且跳动的字符串"1 MBR"
mov byte [gs:0x00],'L'
mov byte [gs:0x01],0xA4 ; A表示绿色背景闪烁,4表示前景色为红色
jmp $ ; 通过死循环使程序悬停在此