《操作系统真象还原》第三章——完善MBR

前置知识

显卡与显存

为了显示文字,通常需要两种硬件,一是显示器,二是显卡。

  • 显卡的职责是为显示器提供内容,并控制显示器的显示模式和状态。
  • 显示器的职责是将那些内容以视觉可见的方式呈现在屏幕上。

除此以外,还需要知道的是

  • 显卡未必一定是独立的插卡。为了节省使用者的成本,有的显卡会直接做在主板上,这样的显卡也有个名字,叫集成显卡。
  • 显卡控制显示器的最小单位是像素。

那么,什么是显存呢?

显存就是存放要在显示器上显示的内容的器件 ,因为它位于显卡上,故称显示存储器(Video RAM,VRAM),简称显存,要显示的内容都预先写入显存。

显卡的工作是周期性地从显存中提取这些比特,并把它们按顺序显示在屏幕上。如果是比特"0",则像素保持原来的状态不变,因为屏幕本来就是黑的;如果是比特"1",则点亮对应的像素。

因此,**为了给出要显示的字符,处理器需要访问显存,把字符的ASCII码写进去。**但是,显存是位于显卡上的,访问显存需要和显卡这个外围设备打交道。同时,多一道手续自然是不好的,这当中最重要的考量是速度和效率。

为此,计算机系统的设计者们,决定把显存映射到处理器可以直接访问的地址空间里,也就是内存空间里。

如图所示,8086可以访问1MB内存。其中,0x00000~9FFFF属于常规内存,由内存条提供;0xF0000~0xFFFFF由主板上的一个芯片提供,即ROM-BIOS。而0xB8000~0xBFFFF这段物理地址空间,是留给显卡的,由显卡来提供,用来显示文本。

屏幕是如何显示内容的

  • 显卡在任何时候都认为你发送的是ASCII码。
  • 屏幕上的每个字符对应着显存中连续2字节,前一个是字符的ASCII代码,后面是字符的显示属性,包括字符颜色(前景色)和底色(背景色)

如图所示,字符的显示属性(1字节)分为两部分

  • 低4位定义的是前景色,高4位定义的是背景色
  • 色彩主要由R、G、B这3位决定
  • K是闪烁位,为0时不闪烁,为1时闪烁
  • I是亮度位,为0时正常亮度,为1时呈高亮。

操作显存显示内容

编写mbr.S代码

bash 复制代码
;主引导程序MBR,由BIOS通过jmp 0:0x7c00跳转

;------------------------------------------
;vstart=0x7c00表示本程序在编译时,起始地址编译为0x7c00
SECTION MBR vstart=0x7c00
;使用通用寄存器中的值(0)初始化其余寄存器
    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,0x0600
    mov bx,0x0700
    mov cx,0        ;左上角(0,0)
    mov dx,0x184f   ;右下角(80,25)
                    ;0x18=24,0x4f=79
    int 0x10        ;调用BIOS中断函数
;------------------------------------------

;将要显示的字符串写入到显存中
    mov byte [gs:0x00],'1';在第一个字节的位置写入要显示的字符"1"
    ;在第二个字节的位置写入显示字符(也就是字符1)的属性,其中A表示绿色背景,4表示前景色为红色
    mov byte [gs:0x01],0xA4

    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

编译

bash 复制代码
nasm -o mbr.bin mbr.S

将程序写入磁盘

bash 复制代码
dd if=./osCode/mbr.bin of=./bochs/hd60M.img bs=512 count=1 conv=notrunc

运行

bash 复制代码
./bin/bochs -f boot.disk

从硬盘中加载程序到内存

boot.inc

bash 复制代码
;-----loader and kernel-----
LOADER_BASE_ADDR equ 0x900;loader在内存中位置
LOADER_START_SECTOR equ 0x2;loader在磁盘中的逻辑扇区地址,即LBA地址

mbr.S

bash 复制代码
;主引导程序MBR,由BIOS通过jmp 0:0x7c00跳转

;------------------------------------------
%include "boot.inc"
;vstart=0x7c00表示本程序在编译时,起始地址编译为0x7c00
SECTION MBR vstart=0x7c00
;使用通用寄存器中的值(0)初始化其余寄存器
    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,0x0600
    mov bx,0x0700
    mov cx,0        ;左上角(0,0)
    mov dx,0x184f   ;右下角(80,25)
                    ;0x18=24,0x4f=79
    int 0x10        ;调用BIOS中断函数
;------------------------------------------

;------------------------------------------
;将要显示的字符串写入到显存中
    mov byte [gs:0x00],'1';在第一个字节的位置写入要显示的字符"1"
    ;在第二个字节的位置写入显示字符(也就是字符1)的属性,其中A表示绿色背景,4表示前景色为红色
    mov byte [gs:0x01],0xA4

    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

    mov eax,LOADER_START_SECTOR;起始扇区地址
    mov bx,LOADER_BASE_ADDR;要写入的内存地址
    mov cx,1;待读入的扇区数目
    call rd_disk_m16;调用磁盘读取程序

    jmp LOADER_BASE_ADDR;

;------------------------------------------
;函数功能:读取硬盘的n个扇区
;参数:
    ;eax:LBA扇区号
    ;bx:要写入的内存地址
    ;cx:待读入的扇区数目

rd_disk_m16:
;out命令,通过端口向外围设备发送数据
;其中目的操作数可以是8为立即数或者寄存器DX,源操作数必须是寄存器AL或者AX
;因此需要将之前ax中保存的值进行备份
    mov esi,eax
    mov di,cx

;正式读写硬盘
;第一步:设置要读取的扇区数目
    mov dx,0x1f2
    mov ax,cx
    out dx,ax

    mov eax,esi;恢复eax的值

;第二步:将LBA的地址存入0x1f3~0x1f6
    ;LBA的0~7位写入端口0x1f3
    mov dx,0x1f3
    out dx,al

    ;LBA的8~15位写入端口0x1f4
    mov cl,8
    shr eax,cl;ax左移8位
    mov dx,0x1f4
    out dx,al

    ;LBA的16~23位写入端口0x1f5
    shr eax,cl
    mov dx,0x1f5
    out dx,al

    ;LBA的24~27位写入端口0x1f6
    shr eax,cl;左移8位
    and al,0x0f;与0000 1111相与,取后四位
    or al,0xe0;设置7~4位为1110,表示lba模式
    mov dx,0x1f6
    out dx,al

;第三步,向0x1f7端口写入读命令,0x20
    mov dx,0x1f7
    mov al,0x20
    out dx,al

;第四步,检测硬盘状态
    .not_ready:
        nop;空操作,什么也不做,相当于sleep,只是为了增加延迟
        in al,dx;由于读取硬盘状态信息的端口仍旧是0x1f7,因此不需要再为dx指定端口号
        
        ;取出状态位的第3位和第7位
        ;其中第3位若为1表示硬盘控制器已经准备好数据传输,第7位为1表示硬盘忙
        and al,0x88
        ;cmp为减法指令,此处是为了判断第7位是否为1,如果为1,则减法的结果应该为0
        cmp al,0x08
        ;JNZ是条件跳转指令,它表示"Jump if Not Zero",
        ;也就是如果零标志位(ZF)不为0,则进行跳转。否则不进行跳转
        jnz .not_ready;若未准备好,则继续等
    
;第五步,从0x1f0端口读取数据
    mov ax,di;di是之前备份的要读取的扇区数
    mov dx,256
    ;mul指令表示乘法操作,当只有一个操作数时,被乘数隐含在al或者ax中
    mul dx;一个扇区512字节,每次读取两个字节,需要读取di*256次
    mov cx,ax;cx在此时表示要循环读取的次数

    mov dx,0x1f0
    .go_on_read:
        in ax,dx
        mov [bx],ax;通过循环将输入写入bx寄存器所指向的内存,每次读入2个字节的数据
        add bx,2
        loop .go_on_read
        ret

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

编译

其中**-I选项表示指定代码中包含的头文件(boot.inc)路径**

bash 复制代码
nasm -I include/ -o mbr.bin mbr.S

将MBR程序写入磁盘的MBR扇区内

bash 复制代码
dd if=./osCode/mbr.bin of=./bochs/hd60M.img bs=512 count=1 conv=notrunc

实现内核加载器

loader.S

bash 复制代码
%include "boot.inc"

SECTION vstart=LOADER_BASE_ADDR
;------------------------------------------
;将要显示的字符串写入到显存中
    mov byte [gs:0x00],'2';在第一个字节的位置写入要显示的字符"1"
    ;在第二个字节的位置写入显示字符(也就是字符1)的属性,其中A表示绿色背景,4表示前景色为红色
    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 $

编译

bash 复制代码
nasm -I ./include/ -o loader.bin loader.S

将程序写入磁盘的2号扇区内

由于0号扇区内写入的是MBR程序,因此loader程序需要写入2号扇区内

bash 复制代码
dd if=./osCode/loader.bin of=./bochs/hd60M.img bs=512 count=1 seek=2 conv=notrunc

运行

相关推荐
kunge201333 分钟前
Ubuntu22.04 安装virtualbox7.1
linux·virtualbox
清溪54934 分钟前
DVWA中级
linux
Sadsvit1 小时前
源码编译安装LAMP架构并部署WordPress(CentOS 7)
linux·运维·服务器·架构·centos
xiaok1 小时前
为什么 lsof 显示多个 nginx 都在 “使用 443”?
linux
苦学编程的谢2 小时前
Linux
linux·运维·服务器
G_H_S_3_2 小时前
【网络运维】Linux 文本处理利器:sed 命令
linux·运维·网络·操作文本
Linux运维技术栈2 小时前
多系统 Node.js 环境自动化部署脚本:从 Ubuntu 到 CentOS,再到版本自由定制
linux·ubuntu·centos·node.js·自动化
拾心213 小时前
【运维进阶】Linux 正则表达式
linux·运维·正则表达式
Gss7774 小时前
源代码编译安装lamp
linux·运维·服务器
444A4E4 小时前
深入理解Linux进程管理:从创建到替换的完整指南
linux·c语言·操作系统