操作系统真象还原 学习笔记03--完善MBR

selph
selph
发布于 2021-01-21 / 1162 阅读
0
0

操作系统真象还原 学习笔记03--完善MBR

本篇简单介绍了MBR程序是怎么从0x7c00加载执行的?MBR如何控制显卡来进行显示?MBR如何读写硬盘数据?以及完善MBR的功能。

MBR的功能是:将内核加载器Loader加载到内存中并跳转过去。

本篇难点:

  • 通过端口IO操作显卡、读写硬盘

地址、Section、Vstart

这里解释了为什么MBR程序会加载在0x7C00并进行执行。

首先,地址是什么?编译器是干嘛的?

地址是个数字,表示各种符号(变量名,函数名等)在源程序中的位置。

编译器负责给各符号编址,就是符号相对于文件头的偏移量


其次,Section是什么?

Section称为节,就是用来人为将汇编程序分成多个部分来使用的,比如一个节存储程序,一个节存储数据。


最后,Vstart是什么?

nasm编译器中,将Section用vstart修饰,可以给Section赋予虚拟地址,例如上一篇程序中的:

SECTION MBR vstart=0x7c00

让这个区段以0x7c00位起始地址。


那么,MBR程序是怎么被加载执行的呢?

MBR程序位于0盘0道1扇区的位置,BIOS在运行过程中,会将MBR加载到0x7c00的位置上,为了让程序正常的运行,那么代码里的地址偏移量也都应该是以0x7c00为起始地址,所以在编码的时候,指定vstart=0x7c00让编译器按照0x7c00为起始地址为程序进行编址,这样,程序被BIOS加载好跳转过去就能直接执行了。

CPU的工作原理

实模式说的是8086CPU的寻址方式、寄存器大小、指令用法等,是用来反应CPU在该环境下如何工作的概念。


CPU的工作是执行指令,CPU的工作原理,其实就是CPU如何执行指令的原理;

CPU分为三个部分:控制单元、运算单元、控制单元。


控制单元是CPU的控制中心,CPU需要通过控制单元来知道下一条执行的指令是什么。

控制单元由指令寄存器IR、指令译码器ID、操作控制器OC组成。

程序运行过程中,指令指针寄存器IP(指令的地址在程序计数器PC上,也就是x86CPU上的cs:ip)指向下一条执行的地址,控制单元根据IP的指向,将位于内存中的指令逐个装载到指令寄存器IR中,然后指令译码器ID将位于指令寄存器IR中的指令按照指令格式来解码,指令格式都是固定的:

IA-32指令格式
前缀		操作码		寻址方式、操作数类型		立即数		偏移量

存储单元指的是CPU内部的L1、L2、L3缓存和寄存器,待处理的数据(指令中的操作数)就放在这里头,寄存器分为程序可见寄存器(通用寄存器,段寄存器等)和程序不可见寄存器

为了提高CPU运行效率,从而出现了缓存,缓存采用SRAM(静态随机存储器),是比内存DRAM(动态随机存储器)性能更好的存储器,但集成度低,容量不大。DRAM需要每隔一段时间去刷新电路,否则数据会消失,SRAM则不用


运算单元负责逻辑运算和算术运算,负责从控制单元那里读取数据,执行计算

实模式下的寄存器

寄存器有一部分是对程序不可见的,如:全局描述符表寄存器GDTR、中断描述符表寄存器IDTR、局部描述符表寄存器LDTR、任务寄存器TR、控制寄存器CR0-3、指令指针寄存器IP、标志寄存器flags、调试寄存器DR0-7;

还有一部分是对程序可见的,如通用寄存器、段寄存器;

对于不可见那一类寄存器,虽然不能直接使用,但其中一部分还得由命令来初始化,这些内容在保护模式会涉及到。

在实模式下,寄存器都是16位宽。


实模式下,内存访问通过“段基址:段内偏移地址”来进行访问,这里的段基址,就是用段寄存器来存储的,不管在实模式还是保护模式下,段寄存器的功能都是指定一片内存区域的起始地址;

段有很多种:

  • CS代码段,把所有指令都连续放在一起,是一个只有指令的区域
  • DS数据段,类似CS段,不过存的都是数据
  • SS栈段,只在内存中有
  • ES、FS、GS附加段,给程序提供的多几个段来用

flags寄存器用来存各种状态,后面专门写一篇来介绍这flags寄存器


通用寄存器,就那些功能,懒得介绍了

实模式被取代的原因

实模式的实指的是它用的是真实的物理地址,现在实模式已经被保护模式取代了,实模式仅存在于引导系统启动的这个阶段,引导完成后,控制器交给保护模式。

实模式被淘汰的主要原因是,安全问题,实模式下,用户程序和系统是同一特权级别,用户程序可以轻易执行一些破坏性的指令,而保护模式则引入了特权级,用户级和系统级不是一个级别,系统的安全性得到了提升。

CPU与外设

CPU通过IO接口与外设通信,IO接口位于CPU和外设之间,用于协助CPU与外设通信,常见的IO接口,如显卡(驱动显示器)、声卡(驱动音响)等;

IO接口可以分为硬件部分和软件部分;

硬件部分,为了简化CPU访问外设的工作,能够轻易同任何外设通信,所以IO的功能是约定好的:

  • 设置数据缓冲,解决CPU与外设速度不匹配的问题
  • 设置信号电平转换电路,将外设的机电电平转换成CPU的TTL电平
  • 设置数据格式转换,A/D、D/A转换器进行数模转换
  • 设置时序控制电路来同步CPU和外设
  • 提供地址译码,使CPU可以选中某个IO端口进行通信

同一时间CPU只能同一个IO接口通信,负责控制CPU同哪一个IO接口通信的设备叫输入输出控制中心,也就是南桥芯片,CPU通过内部总线连接南郊芯片的内部,南桥负责和低速设备通信(北桥负责高速设备),如并口硬盘PATA、串口硬盘SATA、USB、PCI、电源管理等接口,其中,PCI接口是南桥芯片专门用于扩展的接口


软件部分,IO接口是通过寄存器与CPU通信的,为区别CPU的寄存器,所以叫做端口,端口是IO接口开放给CPU的接口,一般IO设备都有一组端口,每个端口都有自己的功能

IA-32体系中,系统把IO接口的端口独立编址,可以使用in和out指令来进行读写操作:

  • in ax dx
  • out dx bx

这里dx是端口号

小结:

  • IO接口负责连接外设和CPU
  • IO接口传输前后会对数据进行处理

显存、显卡、显示器

MBR运行在实模式下,可以用BIOS的0x10中断打印字符串,但在进入保护模式之后,就不能这么操作了,因为BIOS中断依赖中断向量表,保护模式下没中断向量表;

某些IO接口也叫适配器,显卡也可以叫做显示适配器;

显卡是pci设备,插在pci接口上,pci总线是并行传输的,并行传输需要同时接收位宽x的数据,位宽越大越困难,于是有了pcie总线,这是串行传输的,现在的显卡都是串口的了

传输速度取决于并行数据量和传输频率,由于频率很大,目前串行传输速率极高


显卡提供的可编程接口是显存和IO端口,显存是显卡提供的,位于显卡内部的一块内存;

显卡的工作就是不断的读取显存,将其内容发送到显示器,显示器不区分显示的内容是什么,在显示器眼中,显示的东西全都是像素点的排列组合,为了便于显示文字,于是有了编码,例如ASCII编码,这是一种约定,显卡根据编码来显示编码所对应的字符。

实模式的显存分布中,0xB8000~0xBFFFF这32kb空间用于文本模式显示适配器,往0xb8000输出的字符会落入显存中,显存中有了数据就会往显示器上显示;

显卡的文本模式也分很多种模式,用“列*行”来表示,默认是80*25;

每个字符在屏幕上都由两个字符表示,低字节是字符的ASCII码,高字节是属性元信息,低4位是前景色,高4位是字符背景色,第4位控制亮度,第7位控制是否闪烁。

改进MBR,直接操作显卡

主引导程序
;-----------------------------------------------------
SECTION MBR vstart=0x7c00                       ;告诉编译器,起始地址是0x7c00
        mov ax,cs                               ;因为BIOS执行完毕后cs:ip为0x0:0x7c00,所以用cs初始化各寄存器(此时cs=0)
        mov ds,ax                               ;ds、es、ss、fs不能给立即数初始化,需要用ax寄存器初始化
        mov es,ax
        mov ss,ax
        mov fs,ax
        mov sp,0x7c00                           ;初始化堆栈指针,因为目前0x7c00以下的内存暂时可用
        mov ax,0xb800                           ;选择显卡的文本模式
        mov gs,ax                               ;使用GS段寄存器作为显存段基址

;清屏利用0x06号功能,上卷全部行,则可清屏
;-----------------------------------------------------
;INT 0x10 
-----------------------------------------------------
;输入:
;AH     功能号= 0x06
;AL = 上卷行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:

        mov ax,0x600                    ;上卷行数:全部  功能号:06
        mov bx,0x700                    ;上卷属性
        mov cx,0                        ;左上角:(0,0)
        mov dx,0x184f                   ;右下角:(80,25)
                                        ;VGA文本模式中,一行只能容纳80字节,共25行
                                        ;下标从0开始,所以0x18=24,0x4f=79
        int 0x10                        ;int 0x10

;输出背景色为蓝色,前景色为红色,并且跳动的字符串“1 MBR”
;--------------------------------------------------------------------------
mov byte [gs:0x00], '1'
mov byte [gs:0x01], 0x92

mov byte [gs:0x02], ' '
mov byte [gs:0x03], 0x12

mov byte [gs:0x04], 'M'
mov byte [gs:0x05], 0x12

mov byte [gs:0x06], 'B'
mov byte [gs:0x07], 0x12

mov byte [gs:0x08], 'R'
mov byte [gs:0x09], 0x12
;--------------------------------------------------------------------------

        jmp $                                   ;使用程序悬停在此

        message db "hello world"
        times 510-($-$$) db 0
        db 0x55,0xaa

硬盘简介

这里简单介绍一下机械硬盘是怎么工作的;

结构简介:

机械硬盘上有大量盘片,每个盘片上都有两面,每一面都涂有磁性介质,用来存储数据;

每个盘面上都有磁头,磁头臂由步进电机驱动带着磁头进行移动,进行访问磁盘数据;

磁盘被划分为多个区域,以一定半径为圆,划分出多个同心圆环,这个圆环叫做磁道,磁道上以一定大小为基准进行进一步划分,分成一个一个扇形,叫做扇区,一般一个磁道上有63个扇区,磁盘里有大量盘片,不同盘片的同一磁道称为柱面。

为了减少磁头移动的时间,数据存储是以柱面为单位进行的,在当前磁道的扇区里写入数据,写不下了向下一个盘面的当前磁道的这个扇区进行写入;

每个扇区都有自己的Header,上面会记录当前扇区所在的磁头号,磁道号,扇区号。

IO简介:

CPU同磁盘进行控制使用的IO接口叫做硬盘控制器,硬盘控制器和硬盘是连在一起的,这种接口称为集成设备电路IDE,后来改叫ATA,串行接口SATA,并行接口PATA

PATA接口一边插向主板,另一边插向两个硬盘,一个是主盘一个是从盘;主板上如果支持4块这样的硬盘,则会有两个PATA插槽,这个插槽称为通道,分别为Primary通道和Secondary通道;

使用IO:

让硬盘工作,需要通过读写硬盘控制器上的端口(寄存器)来进行:

在这里插入图片描述

  • Data寄存器是用于管理数据的,我们读取和写入的数据都通过这个寄存器获取,此寄存器被设计为16位。
  • Error寄存器,只在读取硬盘失败时才有效,里面才有失败的信息。
  • 尚未读取的扇区数载Sector count寄存器中。
  • LBA寄存器有LBA low、LBA mid、LBA high 三个,它们三个都是8位宽度的。LBA low寄存器用来u才能出28位地址的0 ~ 7位,LBA mid寄存器用来存储第 8 ~ 15 位,LBA high寄存器存储第16 ~ 23位。剩下不够的位数用Device寄存器。
  • device寄存器,它的宽度是8位。在此寄存器的低4位用来存储LBA地址的24~27位。第4位用来指定通道上的主盘或从盘,0代表主盘,1代表从盘。第6位用来设置是否启用LBA方式,1代表启动LBA模式,0代表启用CHS模式。

这里主要使用的命令是:

  1. identify:0xEC,硬盘识别
  2. read sector: 0x20,读扇区
  3. write sector: 0x30,写扇区

硬盘操作方法:

  1. 先选择通道,往该通道的sector count写入待操作扇区数
  2. 往该通道上三个LBA寄存器写入扇区起始地址的低24位
  3. 往device寄存器中写入LBA地址的24~27位,并置第6位为1,使其为LBA模式,设置第4位选择主盘或从盘
  4. 往该通道上command寄存器中写入操作命令
  5. 读取该通道上的status寄存器,判断硬盘工作是否完成
  6. 如果以上步骤是读硬盘进入下一个步骤,否则完工。
  7. 将硬盘数据读出

完善MBR,使用硬盘

操作系统由BIOS引导,BIOS执行完毕后,将位于0盘0道1扇区的MBR程序加载到0x7C00内存中,然后将控制权交给MBR,MBR只有512字节大小,加载不了内核,所以需要在另一个地方加载内核(加载器),MBR的工作就是将加载器从硬盘读取到内存空间中,然后将控制权交给它,就像BIOS将控制权交给MBR一样。

为了安全一点,把加载器程序放在第二扇区上,加载到内存0x900的位置上;

配置文件boot.inc:

SE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
主引导程序
;-----------------------------------------------------
%include "boot.inc"
SECTION MBR vstart=0x7c00                       ;告诉编译器,起始地址是0x7c00
        mov ax,cs                               ;因为BIOS执行完毕后cs:ip为0x0:0x7c00,所以用cs初始化各寄存器(此时cs=0)
        mov ds,ax                               ;ds、es、ss、fs不能给立即数初始化,需要用ax寄存器初始化
        mov es,ax
        mov ss,ax
        mov fs,ax
        mov sp,0x7c00                           ;初始化堆栈指针,因为目前0x7c00以下的内存暂时可用
        mov ax,0xb800                           ;选择显卡的文本模式
        mov gs,ax                               ;使用GS段寄存器作为显存段基址

;清屏利用0x06号功能,上卷全部行,则可清屏
;-----------------------------------------------------
;INT 0x10       功能号:0x06             功能描述:上卷窗口
;-----------------------------------------------------
;输入:
;AH     功能号= 0x06
;AL = 上卷行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:

        mov ax,0x600                    ;上卷行数:全部  功能号:06
        mov bx,0x700                    ;上卷属性
        mov cx,0                        ;左上角:(0,0)
        mov dx,0x184f                   ;右下角:(80,25)
                                        ;VGA文本模式中,一行只能容纳80字节,共25行
                                        ;下标从0开始,所以0x18=24,0x4f=79
        int 0x10                        ;int 0x10

;输出背景色为蓝色,前景色为红色,并且跳动的字符串“1 MBR”
;--------------------------------------------------------------------------
        mov byte [gs:0x00], '1'
        mov byte [gs:0x01], 0x92

        mov byte [gs:0x02], ' '
        mov byte [gs:0x03], 0x12

        mov byte [gs:0x04], 'M'
        mov byte [gs:0x05], 0x12

       mov byte [gs:0x06], 'B'
        mov byte [gs:0x07], 0x12

        mov byte [gs:0x08], 'R'
        mov byte [gs:0x09], 0x12
;--------------------------------------------------------------------------


        mov eax ,LOADER_START_SECTOR    ;起始扇区lba地址
        mov bx  ,LOADER_BASE_ADDR       ;写入的地址
        mov cx  ,1                      ;待读入的扇区数
        call rd_disk_m_16               ;以下读取程序的起始部分

        jmp LOADER_BASE_ADDR			;跳转到Loader

;--------------------------------------------------------------------------
;功能:读取eax=LBA扇区号
rd_disk_m_16:
        mov esi ,eax    ;备份eax
        mov di ,cx      ;备份cx

;读写硬盘
;1---设置要读取的扇区数
        mov dx ,0x1f2   ;设置端口号,dx用来存储端口号的
        mov al ,cl
        out dx ,al      ;读取的扇区数

        mov eax ,esi    ;恢复eax


;2---将LBA地址存入0x1f3~0x1f6
        ;LBA 7~0位写入端口0x1f3
        mov dx ,0x1f3
        out dx ,al

        ;LBA 15~8位写入端口0x1f4
        mov cl ,8
        shr eax ,cl     ;逻辑右移8位,将eax的最低8位移掉,让最低8位al的值变成接下来8位
        mov dx ,0x1f4
        out dx ,al

        ;LBA 24~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    ;第三位为1表示已经准备好了,第7位为1表示硬盘忙
        cmp al ,0x08
        jnz .not_ready

;5---0x1f0端口读取数据
        mov ax ,di      ;要读取的扇区数
        mov dx ,256     ;一个扇区512字节,一次读取2字节,需要读取256次
        mul dx          ;结果放在ax里
        mov cx ,ax      ;要读取的次数

        mov dx ,0x1f0
.go_on_read:
        in ax, dx
        mov [bx], ax    ;bx是要读取到的内存地址
        add bx, 0x02
        loop .go_on_read        ;循环cx次
        ret

        times 510-($-$$) db 0
        db 0x55,0xaa

编译:

nasm -I include/ -o mbr3.bin mbr3.S 

-I参数用来指定include文件位置

加载到硬盘中:

dd if=./boot/mbr3.bin of=hd60M.img bs=512 count=1 conv=notrunc

随便写个程序来代替loader

主要是来试试看这个MBR将控制权交给Loader程序是否成功,Loader程序:

%include "boot.inc"
SECTION LOADER vstart=LOADER_BASE_ADDR

mov byte [gs:0x00], '2'
mov byte [gs:0x01], 0xA4

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 $

就是上面操作显卡的时候的显示字符串,这里不用手动设置gs段寄存器是因为在这个程序执行之前,段寄存器已经是设置好的了

编译:

nasm -I include/ -o loader.bin loader.S 

dd:

dd if=boot/loader.bin of=hd60M.img bs=512 count=1 seek=2 conv=notrunc

通过seek选择跳过块的数量,因为使用第二个扇区,所以跳过0和1两个块(块指定的大小是512字节和扇区相同)

运行Bochs

程序也写完了,来看看效果吧:

image-20210121005249615

参考资料


评论