完善MBR
地址、section、vstart
什么是地址
地址只是数字,是描述各种符号在源程序中的位置,他是源代码文件中各符号偏移文件开头的距离。编译器给程序中各符号分配的地址就是各符号相对于文件开头的偏移量。
什么是section
section描述汇编语言中的段,只是为了让程序员在逻辑上将程序分为几个部分,他是伪指令,CPU不知道其存在
什么是vstart
section 使用vstart=修饰后,可以被赋予一个虚拟起始地址,被用来计算在该section内所有的内存引用地址,即根据此地址是在文件中找不到相关数据的,使用的条件是已知程序会被放入确定的地址中。
为什么mbr用vstart=0x7c00修饰
因为开发人员知道mbr要被加载器BIOS加载到物理地址0x7c00,mbr后续的物理地址都是0x7c00。
CPU的实模式
CPU的工作原理
CPU大体上可以分为三个部分:控制单元、运算单元、存储单元。
控制单元是控制中心,由指令寄存器IR、指令译码器ID、操作控制器OC组成;存储单元是CPU内部的L1,L2缓存(SRAM,即静态随机访问存储器)及寄存器;运算单元负责算术运算和逻辑运算,只是一个执行部件。
实模式下的寄存器
寄存器是一种物理存储元件,比一般的存储介质快,能够跟上CPU的速度。分为两类:内部使用(全局描述符表寄存器GDTR,中断描述符表寄存器IDTR…);对程序员可见(段寄存器、通用寄存器)。在实模式下,都是16位的。
实模式下的CPU寻址方式
寻址方式可以分为三类:
- 寄存器寻址
- 立即数寻址
- 内存寻址:操作数在内存中的寻址方式,分为
- 直接寻址
- 基址寻址:实模式下只能用bx(DS)、bp(SS)作为基址寄存器
- 变址寻址
- 基址变址寻址
为什么要有栈
栈是线性表的一种,且在栈顶对数据进行存取。段基址寄存器SS,另一端是动态的,用段指针寄存器SP来指定,SP也是栈的出口跟入口。栈是从高地址向低地址发展,所以指针指向的地址会越来越低,使用pop和push进行操作
ret、call、jmp、flags
因为调用函数会进行多次,所以需要保留多个CS:IP中的IP值(可能有CS),将寄存器IP值(即返回地址)存入栈中。ret功能是在栈顶弹出2字节的内容来替换IP寄存器,ret不看内容,仅弹出为IP寄存器赋值。retf是从栈顶取4字节,栈顶处的2字节替换IP寄存器,剩下的2字节替换CS寄存器。ret用于近返回,retf用于远返回。
jmp是一去不返的执行新代码,call是执行一段分支后返回。
call在16位实模式相对近调用:”近“指在同一个段内,不用换段基址,只需要给出段内偏移地址就行,用关键字near
修饰;“相对”指用于是在同一个代码段内,只要给出目标函数的相对地址即可,有正负之分。
call在16位实模式间接绝对近调用:“间接”指目标函数的地址没有给出,地址在寄存器或者内存中,不是立即数;“绝对”指目标函数的地址是绝对地址
call在16位实模式直接绝对远调用:操作数(立即数)在指令中直接给出,不需要经过寄存器或内存;”远“,即跨段
call在16位实模式间接绝对远调用:段基址和段内偏移地址的值在内存或者寄存器中。
jmp在16位实模式相对短转移:jmp $,即jmp short 立即数地址
jmp在16位实模式相对近转移:jmp $,即jmp near 立即数地址
jmp在16位实模式间接绝对近转移:jmp near 寄存器/内存地址
jmp在16位实模式直接绝对远转移:jmp near 立即数
jmp在16位实模式间接绝对远转移:jmp near 内存地址
flags:标志寄存器
有条件转移:jxx 目标地址
显卡与接口
接口功能
- 设置数据缓冲,解决CPU与外设的速度不匹配
- 设置信号电平转换电路
- 设置数据格式转化
- 设置时序控制电路来同步CPU和外部设备+
- 通过地址译码
ICH
同一时刻,CPU只能和一个IO接口进行通信,使用输入输出控制中心(ICH)解决IO接口竞争问题,还要连接各种内部总线,也就是南桥芯片。
改进MBR
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| ;主引导程序 ; ;LOADER_BASE_ADDR equ 0xA000 ;LOADER_START_SECTOR equ 0x2 ;------------------------------------------------------------ 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
; 清屏 ;利用0x06号功能,上卷全部行,则可清屏。 ; ----------------------------------------------------------- ;INT 0x10 功能号:0x06 功能描述:上卷窗口 ;------------------------------------------------------ ;输入: ;AH 功能号= 0x06 ;AL = 上卷的行数(如果为0,表示全部) ;BH = 上卷行属性 ;(CL,CH) = 窗口左上角的(X,Y)位置 ;(DL,DH) = 窗口右下角的(X,Y)位置 ;无返回值: mov ax, 0600h mov bx, 0700h mov cx, 0 ; 左上角: (0, 0) mov dx, 184fh ; 右下角: (80,25), ; 因为VGA文本模式中,一行只能容纳80个字符,共25行。 ; 下标从0开始,所以0x18=24,0x4f=79 int 10h ; int 10h
; 输出背景色绿色,前景色红色,并且跳动的字符串"1 MBR" mov byte [gs:0x00],'1' mov byte [gs:0x01],0xA4 ; A表示绿色背景闪烁,4表示前景色为红色
mov byte [gs:0x02],' ' mov byte [gs:0x03],0xA4
mov byte [gs:0x04],'M' mov byte [gs:0x05],0xA4
mov byte [gs:0x06],'B' mov byte [gs:0x07],0xA4
mov byte [gs:0x08],'R' mov byte [gs:0x09],0xA4
jmp $ ; 通过死循环使程序悬停在此
times 510-($-$$) db 0 db 0x55,0xaa
|
因为是循环,退出需要进入config然后选择quit
显卡
某些IO接口也叫适配器,适配器是驱动某一外部设备的功能模块,显卡也称为显示适配器,归根结底就是IO接口,专门用来连接CPU和显示器。显卡是PCI设备。
显存由显卡提供,它是位于显卡内部的一块内存。
硬盘
扇区定位
磁头号、磁道号、扇区号才能定位一个扇区
LBA(Logical Block Address)逻辑块地址,分为LBA28模式和LBA48模式(分别使用28/48位比特来描述一个扇区的地址),使用LBA寄存器定位(LBA low(0-7),LBA mid(8-15),LBA high(16-23)),剩下的使用device寄存器(8位,低4位为24-27,剩下的4位中,第1位是指定主盘或从盘,第3位是是否启用LBA,1代表启用,0代表CHS模式)
工作原理
针对硬盘的IO接口是硬盘控制器,硬盘和硬盘控制器是在一起的,这种接口被称为集成设备电路IDE,此接口规范化之后产生了ATA(Advanced Technology Attachment),硬盘串行接口SATA、并行接口PATA。
让硬盘工作就需要读取硬盘控制器的端口,端口就是位于IO控制器上的寄存器。
寄存器
在读硬盘时,端口0x1F7或0x177的寄存器是Status(8位),P127页,具体不展开了
在写硬盘时,…是Command,同上
三个命令:
- identify:0xEC,即硬盘识别
- read sector:0x20,读扇区
- write sector:0x30,写扇区
注: error/feature,status/command这两组是同一寄存器,也就是同一端口,多个用途,分别针对读写不同情况来定。
基本步骤




MBR使用硬盘
MBR(第0扇区)负责从硬盘上把loader(第二扇区)加载到内存(原则上0x500-0x7BFF或者0x7E00-9FBFF都可以)。
loader还要加载GDT等,且loader不能被覆盖,内存地址向高处发展,所以放在0x900
include文件夹中的boot.inc.
1 2 3
| ;------------- loader和kernel ---------- LOADER_BASE_ADDR equ 0x900 LOADER_START_SECTOR equ 0x2
|
mbr.S
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
| ;主引导程序 ;------------------------------------------------------------ %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
; 清屏 ;利用0x06号功能,上卷全部行,则可清屏。 ; ----------------------------------------------------------- ;INT 0x10 功能号:0x06 功能描述:上卷窗口 ;------------------------------------------------------ ;输入: ;AH 功能号= 0x06 ;AL = 上卷的行数(如果为0,表示全部) ;BH = 上卷行属性 ;(CL,CH) = 窗口左上角的(X,Y)位置 ;(DL,DH) = 窗口右下角的(X,Y)位置 ;无返回值: mov ax, 0600h mov bx, 0700h mov cx, 0 ; 左上角: (0, 0) mov dx, 184fh ; 右下角: (80,25), ; 因为VGA文本模式中,一行只能容纳80个字符,共25行。 ; 下标从0开始,所以0x18=24,0x4f=79 int 10h ; int 10h
; 输出字符串:MBR mov byte [gs:0x00],'1' mov byte [gs:0x01],0xA4
mov byte [gs:0x02],' ' mov byte [gs:0x03],0xA4
mov byte [gs:0x04],'M' mov byte [gs:0x05],0xA4 ;A表示绿色背景闪烁,4表示前景色为红色
mov byte [gs:0x06],'B' mov byte [gs:0x07],0xA4
mov byte [gs:0x08],'R' mov byte [gs:0x09],0xA4 mov eax,LOADER_START_SECTOR ; 起始扇区lba地址 mov bx,LOADER_BASE_ADDR ; 写入的地址 mov cx,1 ; 待读入的扇区数 call rd_disk_m_16 ; 以下读取程序的起始部分(一个扇区) jmp LOADER_BASE_ADDR ;------------------------------------------------------------------------------- ;功能:读取硬盘n个扇区 rd_disk_m_16: ;------------------------------------------------------------------------------- ; eax=LBA扇区号 ; ebx=将数据写入的内存地址 ; ecx=读入的扇区数 mov esi,eax ;备份eax 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 in al,dx and al,0x88 ;第4位为1表示硬盘控制器已准备好数据传输,第7位为1表示硬盘忙 cmp al,0x08 jnz .not_ready ;若未准备好,继续等。
;第5步:从0x1f0端口读数据 mov ax, di mov dx, 256 mul dx mov cx, ax ; di为要读取的扇区数,一个扇区有512字节,每次读入一个字, ; 共需di*512/2次,所以di*256 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.S
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| %include "boot.inc" section loader vstart=LOADER_BASE_ADDR
; 输出背景色绿色,前景色红色,并且跳动的字符串"1 MBR" mov byte [gs:0x00],'2' mov byte [gs:0x01],0xA4 ; A表示绿色背景闪烁,4表示前景色为红色
mov byte [gs:0x02],' ' mov byte [gs:0x03],0xA4
mov byte [gs:0x04],'L' mov byte [gs:0x05],0xA4
mov byte [gs:0x06],'O' mov byte [gs:0x07],0xA4
mov byte [gs:0x08],'A' mov byte [gs:0x09],0xA4
mov byte [gs:0x0a],'D' mov byte [gs:0x0b],0xA4
mov byte [gs:0x0c],'E' mov byte [gs:0x0d],0xA4
mov byte [gs:0x0e],'R' mov byte [gs:0x0f],0xA4
jmp $ ; 通过死循环使程序悬停在此
|
文件相关:
编译:
nasm -I include/ -o mbr.bin mbr.S
nasm -I include/ -o loader.bin loader.S
写入硬盘
dd if=/root/bochs-2.7/mbr.bin of=/root/bochs-2.7/hd60M.img bs=512 count=1 conv=notrunc
dd if=/root/bochs-2.7/loader.bin of=/root/bochs-2.7/hd60M.img bs=512 count=1 seek=2 conv=notrunc