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

配合视频学习体验更佳!https://www.bilibili.com/video/BV1Rk4y1L7p2/?vd_source=701807c4f8684b13e922d0a8b116af31

代码仓库地址:https://github.com/xukanshan/the_truth_of_operationg_system

在上一章中,我们是通过bios中断来实现打印字符串。现在,我们尝试绕过bios来直接来显示字符。这是为了应对进入保护模式后不能使用bios中断做准备。

能这样的原理是因为显卡的内存已经编排到了cpu能够寻址的范围之内,当cpu操作这部分“内存”时,实际上是直接在和显卡打交道。显卡拿到了数据处理之后,显示器最终会按照要求显示这些数据。内存中的显存映射的地址范围如下:
在这里插入图片描述
显示器上每个字符占两字节,低字节是字符ASCII码,高字节是用来控制颜色的。高字节低4位是字符前景色,也就是字符的颜色(RGB是红蓝绿三种颜色的调和,I位表示是否高亮),高字节高4位是字符的背景色(RGB是红蓝绿三种颜色的调和,K位控制是否闪烁)。所以我们向显卡的对应内存操作时,也应按照如下格式:
在这里插入图片描述
RGB配色的效果如下表:
在这里插入图片描述
接下来我们编写代码,来直接感受与显卡打交道

p110代码mbr.S剖析:

1、代码功能

不使用bios中断的显示字符串功能,直接使用显卡在内存中的映射来显示字符

2、实现原理

显卡将自己的内存编排在了CPU可以寻址的范围之内,CPU可寻址范围中有一块区域是显卡的显存。通过对这块内存区域进行特定的操作,可以与显卡打交道,进而与显示器打交道来显示内容

3、代码逻辑

A、清屏

B、显示字符

4、怎么写代码?

A、指定vstart=0x7c00,这是告诉编译器把本程序的起始地址编译为0x7c00;

B、查询并调用bios中断来进行清屏(在之后甚至直接与显卡打交道来实现此功能);

C、指定段基址,用gs=0xb800,b800是由于显卡内存在内存中的位置决定。gs是随意选择的,也可以选择es,因为我们是用[段基址:偏移]的形式来访问显卡内存,并用规定的格式向0xb8000([段基址:偏移])开始的位置移入字符与颜色设定;

D、死循环;填入MBR规定510字节大小剩下的0;固定结尾两字节0x55,0xaa

5、代码实现如下 (myos/boot/mbr.S)

                                        ;主引导程序 
                                        ;
                                        ;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

6、其他代码详解查看书p111

接下来我们要用MBR做点实事了,MBR只有510B,能做的事情非常少,所以不能指望它做完所有事情。所以,我们用它把操作系统的loader加载到指定位置,然后跳转到loader执行,loader由于大小可以比MBR大得多,所以能做的就很多了。所以,MBR要加载loader,就必须要和磁盘打交道。打交道的方式很简单,就是通过in 与out指令与磁盘暴露在外的寄存器交互。下图是in与out指令的用法:
在这里插入图片描述
磁盘端口寄存器对应的用途(详细请见书p127)
在这里插入图片描述
其中Status与Device寄存器比较复杂,它们的结构的含义如下:
在这里插入图片描述
虽然操作磁盘很复杂,但都是有章可循的,按照如下步骤操作磁盘即可:
在这里插入图片描述
我们现在来写一个mbr程序,来实现从磁盘中加载我们的loader

p131剖析mbr.S代码:

1、代码功能

从磁盘中加载操作系统的loader,该loader由我们自己写入磁盘

2、实现原理

计算机发展到现在,已经将对磁盘的种种操作,简化成了对磁盘暴露在外的寄存器的操作,对这些寄存器的操作需要通过in与out指令

3、代码逻辑

A、清屏

B、通过对内存特定区域的操作显示字符

C、从磁盘特定区域读取特定大小的数据到特定内存位置中

4、怎么写代码?

A、include boot.inc,这里面定义了loader在磁盘中的位置(我们会将其写入磁盘2号扇区),与loader加载进入内存后将要存放的位置(在第二章的内存布局图中,找一个靠前的可用位置就行了,本代码用的是0x900)

B、定义vstart=0x7c00,调用bios中断清屏,对特定内存区域放入数据来显示字符

C、按照与磁盘打交道的7个步骤来完成从磁盘取出数据存放到内存指定区域(这7个步骤就是用in与out操作特定通道的寄存器)

E、跳转到内存中的loader位置执行

F、填充MBR要求的510字节剩下的0,定义MBR要求的标准结尾0x55,0xaa

5、代码实现如下 (myos/boot/include/boot.inc)

                                    ;-------------	 loader和kernel   ----------
LOADER_BASE_ADDR equ 0x900 
LOADER_START_SECTOR equ 0x2

myos/boot/mbr.S

                                    ;主引导程序 
                                    ;------------------------------------------------------------
%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	                    ; 设置74位为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                      ;di当中存储的是要读取的扇区数
    mov dx, 256                     ;每个扇区512字节,一次读取两个字节,所以一个扇区就要读取256次,与扇区数相乘,就等得到总读取次数
    mul dx                          ;8位乘法与16位乘法知识查看书p133,注意:16位乘法会改变dx的值!!!!
    mov cx, ax	                    ; 得到了要读取的总次数,然后将这个数字放入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

6、其他代码详解查看书p132

然后我们写个简单的显示字符的程序loader.S(512字节),用dd命令放入磁盘的2号分区中,来检验我们的MBR能够正常加载并跳转到这个程序中执行,以下是代码,代码剖析略。 (myos/boot/loader.S)

%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编译我们的mbr.s与loader.s,命令:nasm -o xxx xxx.s -I include/。-I意为包含指定的库文件。在/include目录下我们放入boot.inc

用dd命令将mbr写入磁盘0号分区,将loader写入磁盘的2号分区(dd if=loader of=/bochs/hd60M.img seek=2 bs=512 count=1 conv=notrunc,seek意为跳过多少分区的意思)。

猜你喜欢

转载自blog.csdn.net/kanshanxd/article/details/130734274