《操作系统真象还原》第三章——完善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

运行

相关推荐
陈煜的博客5 分钟前
paddleOcr -Linux 下的 Conda 安装
linux·运维·conda
挥剑决浮云 -44 分钟前
Linux 网络编程
linux·运维·c语言·网络·笔记
北 染 星 辰1 小时前
Linux---常用shell脚本
linux·运维·服务器
首席CEO2 小时前
Shell基础2
linux·kali·shell编程
TsengOnce3 小时前
Docker安装稳定版本nginx-1.26.2
linux·nginx·docker
Raymond运维3 小时前
Rocky、Almalinux、CentOS、Ubuntu和Debian系统初始化脚本v9版
linux·运维·ubuntu·centos·debian·rocky·almalinux
荣世蓥3 小时前
6. ARM_ARM指令寻址
linux·运维·arm开发
@daviiid4 小时前
关于adb shell登录开发板后terminal显示不完整
linux·adb
LuckyLay4 小时前
Ubuntu安装配置MySQL(远程登录)
linux·mysql·ubuntu·远程登录
sakabu4 小时前
Linux应用项目之量产工具(三)——文字系统
linux·学习·freetype·量产工具·linux项目