[3.1] 操作系统学习 (深入了解并完善 Mbr)

·

5 min read

查看本系列文章目录

下列内容可能有出错的地方, 毕竟只是个人理解, 有问题评论区🥲🥲

本篇文章将会让你了解一点关于汇编的知识, 还会介绍一下 CPU 实模式, 最后我们会完善 MBR 分区

MBR 是怎么输出 HelloWorld

上篇文章我们让 MBR 输出了 HelloWorld, 接下来我们要了解其中的奥秘

我们先看看SECTION MBR vstart=0x7c00, 看一眼代码我们发现了 3 个关键词

  • SECTION
  • vstart
  • 0x7c00

先说说 SECTION 这个单词的意思是'段', 它可以把我们的代码分成一段一段的, 便于我们理解代码, CPU 根本不知道有这个东西, 代码的地址偏移也没有改变

SECTION s1
  mov a,3
SECTION s2
  mov a,2
SECTION s3
  mov ax,1

其实这段代码就等于

mov a,3
mov a,2
mov a,1

接下来是 vstart 这是用来修饰 SECTION, 可以更改这个段的虚拟起始地址, 也就是说这个程序是从 0x7c00 开始起编的

然后是下一行的mov ax,cs这是赋值语句用 c 语言表示就是 ax=cs, 其中的 ax,cs 都是寄存器

mov [0xc00],1  ;在内存 0xc00 写入 1 . 使用 [地址] 可以表达内存地址
mov ax,0 ;在寄存器 ax 输入 0

还有就是 int 就是中断指令, 我们在第0 篇文章有说过, 和 c 语言中的函数很像

mov ah, 3
mov bh, 0
int 0x10
//这样等于 c 语言中的 (注:0x10 假设为函数名)
0x10(3,0)

中断详细请查看第 0 篇

什么是 CPU 的实模式?

实模式是指 8086 CPU 的寻址方式、寄存器大小、指令用法等,是用来反应 CPU 在该环境下如何工 作的概念。所以想了解实模式这种抽象的概念,主要就是了解在实模式下 CPU 能做什么。 --操作系统真象还原

在这之前, 我们先讲讲CPU工作过程

CPU 大体上可以划分为 3 个部分,它们是控制单元、运算单元、存储单元, 如图

1.png

而他们的工作过程如下图

2.png

这里没有将那么详细, 想要更详细可以查看<<操作系统真象还原>>3.1, 或者 OSDev 的 CPU 部分

实模式

其实我们前面的所有东西都是在讲 实模式 , 我们在这里重新整理一下.

为什么会出现实模式呢? 这主要是为了和新来的 保护模式 区分开.

实模式, 从名字上就可以看出来, 实.

我们在实模式下访问地址都是真实的物理地址, 内存分段产生的逻辑上的地址就是物理地址.

实模式下的寄存器

寄存器其实就是一直比内存更快,但容量小的储存单元.

我们可以用它来储存一下重要的信息, 如段基址...

我们简单用来概括他的作用:

8.png

实模式下的内存分段

其实本来是没有内存分段这种奇奇怪怪的东西的.

很久以前, 程序员都是直接使用绝对的内存地址, 这样的坏处在于如果 2 个程序都要使用同一个内存地址, 就要等另一个程序使用完成, 后一个程序才能使用, 这样就很浪费时间. 所以 CPU 开发者为了解决这个文件就发明了内存分段

关于内存分段的用法, 段偏移+段基址, 在汇编中我们的默认段基址寄存器是DS(数据段寄存器)

我们想要访问 0xa00 可以使用 0x500+0x500 或者是 0x800+0x200 等等...

我们通过一个简单的例子看看

mov 1,[0x1234]   ;没指定段寄存器,默认使用 DS ,
mov 1,[CS:0x1234] ;指定段寄存器 CS. 
; 假设 DS  CS 都为 0x1000.
; 我们这就是在访问 0x1000 + 0x1234 = 0x2234.

好了现在这个问题解决了, 但是还有一个问题还没有解决呢! 我们都知道以前最正常的电脑也有 1mb 的内存吧, 都是就算我们把段基址和段偏移加到最大也只能访问 64kb 的空间啊! 这时有人就说了可是我加到最大,可以访问 20 位的空间啊,

大家先来看看他是怎么加的 0xffff+0xffff=0x1fffe , 确实可以访问到 20 位的空间, 但是他溢出了, 所以这种方法是行不通的.

所以 intel 的工程师在硬件上动了手脚, 把我们每次访问的段基址都乘以 16(左移 4 位) 在加上段偏移,这样不就有 20 位了吗? 虽然看上去很量子力学, 但是却能行得通, 不信我们看看例子

// 假设段基址为:0x1000, 偏移为:0x1234
0x1000 * 16 + 0x1234 = 0x10000 + 0x1234 = 0x11234
// 我们再来算算 0xffff
0xffff * 16 + 0xffff = 0xffff0 + 0xffff = 0x10FFEF
// 等于 0x10FFEF 这不是又溢出了吗?别急,请看下面

我们以 intel8086 为准, 8086 的地址总线只有 20 位, 也就是说他只能使用 1m 的内存,超出1M的内存空间在逻辑上是正常的, 但在物理内存中却无法访问, 超过1MB的内存区域被称为高端内存区(HMA).

所以当程序员给出超过1M的地址时, 系统并不认为其访问越界而产生异常, 而是自动从重新0开始计算, 也就是说系统计算实际地址的时候是按照对1M求模的方式进行的, 这种技术被称为wrap-around.

其实这里还引出一个问题, 就是 a20 地址线的问题, 在后来 cpu 更新换代,有了 a24地址线, 这时的超出部分可以访问, 这里简单带过, 我们等保护模式时再讲

实模式下的内存寻址

简单的说就是cpu 寻找内存地址的方式, 这玩意简单且后期会讲, 所以简单用一张图概括

2.png

在显示器上画画

在前一篇中, 我们通过 BIOS 在屏幕上输出了 HelloWorld ,接下来我们将了解怎么在屏幕上画画, 以及为我们读取硬盘铺垫

CPU 与外设的通讯

在 0 章时, 我们已经稍微了解过 CPU 与外设之间的通讯, 我们现在将深入了解.

由于各种外设的标准不同, 使用为了统一, CPU 使用 IO 接口 来与他们通讯

IO 接口的形式是不限的, 可以是一个芯片, 一个接口...

IO接口是连接 CPU 与外部设备的逻辑控制部件, 它分为硬件和软件两部分; 硬件部分所做的都是一些实质具体的工作, 其功能是协调 CPU 和外设之间的种种不匹配(如双方由于速度不匹配, 那 IO 接口就实现数据缓冲以减少等待时间, 数据格式不匹配, IO 接口就在这两种格式间互相转换); IO 接口内部实际上也是由软件来控制运作的, 这就是所谓的“逻辑”部分,所以软件是指用来控制接口电路工作的驱动程序以及完成内部数据传输所需要的程序.

IO 接口还分为可编程和不可编程的, 在一些简单的接口我们随便就可以使用不可编程的接口(如 USB), 还有些功能比较多的的接口就会使用可编程接口

一个 IO 接口的工作大概是这样的:

1. 设置缓冲区

因为 CPU 的运行速度很快, 但是外设却跟不上 CPU 的速度, 这样就会拖慢 CPU 的速度. 所以 IO 接口设置了数据缓冲区, CPU 只要访问就行了, 不需要等待外设工作完成

2. 信号电平转换

因为 CPU 的信号电平和外设的电平可能不同, 所以需要一个电平转换电路来解决, 这就相当于翻译

3. 数据格式转换

外设的种类是很多的, 他们的数据格式也各不相同(如 数字信号、模拟信号...) ,但 CPU 只能处理数字信号. 所以 IO 接口会转换他们

4. 时序控制, 同步 CPU 和外设

硬件和 CPU 的工作会按照一定的时序, 但是他们是不同的, 只时就需要时序控制了

5. 设置地址译码

一个 IO 接口上有许多端口(寄存器), 但是CPU 一次只能访问一个端口, 这就需要 IO 接 口提供地址译码电路, 这样 CPU 就可以访问自己需要访问的端口了


同一时刻 CPU 只能和一个 IO 接口通讯, 但是如果有许多个 IO 接口通讯想和 CPU 通讯怎么办呢?

这肯定是有主次关系的, 南北桥芯片就是为此而生, 他们连接各种内部总钱, 调节 IO 接口间的通讯顺序

其中南桥芯片负责速度慢的外设(如 PCI设备, USB 端口, 硬盘 SATA), 北桥芯片负责速度快的外设(如 内存)

补充:

由于南桥和北桥一般是成对出现的,至少在支持 Intel CPU 的主板中是这样的(话说, AMD 为了减少 CPU 同北桥交换数据的成本,已经把北桥的工作放到了 CPU 内部,所以支持AMD的板子上未必有北桥芯片) --操作系统真象还原

3.png

访问端口

端口其实就是寄存器, 我们访问寄存器使用 mov, 但端口是 in/out,

;ax,dx 是 CPU 寄存器,dx 里面是端口号
;只要 用in 指令,源操作数(端口号)必须是dx,目的操作数是用 ax,还是 ax,取决于 dx 端口指代的寄存器是 8 位宽度,还是 16 位宽度

in ax,dx  ;读数据
in al,dx

;out 就没 in 那么多限制
out dx,ax
out dx,al
out 端口立即数, al
out 端口立即数, ax

总结一下:

  • dx 只做端口号
  • in 和 out 的操作数位置相反
  • 8 位数据都用 al 储存,16 位数据都用 ax 储存
  • in 只能用 dx 做端口号
  • out 可以使用 dx 或立即数做端口号

终终终终终终终于讲到 显卡 了

上次我们用 BIOS 的功能在屏幕打印了 hellowrold, 但是我们马上就不能使用 BIOS 的功能了, 所以我们要在这之前学会实现 BIOS 的功能, 我们先从最简单的自己打印 HelloWorld 开始(编写出 Print 函数)

显卡介绍

某些 IO 接口也叫适配器,适配器是驱动某 一外部设备的功能模块。显卡也称为显示适配器,不过归 根结底它就是 IO 接口,专门用来连接 CPU 和显示器。我们想操作显示器,没有直接的办法,只能通过它 的 IO接口一一显卡。

稍微说一下显卡。自从几年前 AMD 把 ATI 收购之后,市面上的显卡就分为两大类了, A 卡和 N 卡 。 A 卡是指以 A扣E 为阵营的显卡厂商, N 卡是以 nvidia 为阵营的显卡厂商。大家平时见到的七彩虹、技嘉、 昂达之类的显卡,它们-用的核心要么是 A 卡,要么是 N 卡,有的厂商两个核心都用,开发各自的版本。 他们不自己研发 GPU (显卡的 CPU 称为 GPU),只是在人家的基础上做本地化开发。这种关系就像安卓 手机和安卓原生系统一样 。 --操作系统真象还原

这就不得不说句 AMDyes

我想各位是个人都懂啥是显卡, 所以这就不多说了

操作显卡

需要操作显示器首先就要操作显卡, 而要操作显卡就要操作 IO 接口和显存, 显卡就是不断读取显存, 然后根据显存内容操作显示器的.

在黑白图形显示模式中, 我们使用 0代表黑,1 代表黑. 在 24 位彩色模式中, 我们采用 24 位表示一个像素的方式表达颜色.

我们还可以在显存里写入一字节的ASCII 编码(对照表), 来打印文字.

我们要先将显存映射到内存里. 位置如下图:

4.png

这其中会牵扯到一个问题, 我现在也还没搞清, 找到了大概的东西, 知乎

我们想要在屏幕打印字,还有设置列数,和颜色, 显卡的默认列数位 80*25(2000 个字符)

屏幕上每个宇符的低宇节是字符的 ASCII 码,高字节是字符属性元信息。在高字节中,低 4 位是字符前景色,高 4 位是字符 的背景色。颜色用 RGB 红绿蓝 三种基色调和,第 4 位用来控制亮 度,若置 1 则呈高亮,若为 0 则为一般正常亮度值。第 7 位用来 控制字符是否闪烁(不是背景闪烁),如图

5.png

6.png

更改我们 mbr 代码

我们要开始改进我们的 mbr 代码, 给他增加一点操作显卡的能力

;这是原<<操作系统真象还原>>的代码
;主引导程序 
;
;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

编译:

fengzi@fengzi-ubuntu:~/WorkSpace/Study_OS/code/c3/a/boot$ ls
mbr  mbr.S
fengzi@fengzi-ubuntu:~/WorkSpace/Study_OS/code/c3/a/boot$ ls
mbr  mbr.S
fengzi@fengzi-ubuntu:~/WorkSpace/Study_OS/code/c3/a/boot$ bximage
========================================================================
                                bximage
  Disk Image Creation / Conversion / Resize and Commit Tool for Bochs
         $Id: bximage.cc 13481 2018-03-30 21:04:04Z vruppert $
========================================================================

1. Create new floppy or hard disk image
2. Convert hard disk image to other format (mode)
3. Resize hard disk image
4. Commit 'undoable' redolog to base image
5. Disk image info

0. Quit

Please choose one [0] 1

Create image

Do you want to create a floppy disk image or a hard disk image?
Please type hd or fd. [hd] 

What kind of image should I create?
Please type flat, sparse, growing, vpc or vmware4. [flat] 

Choose the size of hard disk sectors.
Please type 512, 1024 or 4096. [512] 

Enter the hard disk size in megabytes, between 10 and 8257535
[10] 

What should be the name of the image?
[c.img] 

Creating hard disk image 'c.img' with CHS=20/16/63 (sector size = 512)

The following line should appear in your bochsrc:
  ata0-master: type=disk, path="c.img", mode=flat
fengzi@fengzi-ubuntu:~/WorkSpace/Study_OS/code/c3/a/boot$ dd if=/home/fengzi/WorkSpace/Study_OS/code/c3/a/boot/mbr of=/home/fengzi/WorkSpace/Study_OS/code/c3/a/boot/c.img bs=512 count=1  conv=notrunc
1+0 records in
1+0 records out
512 bytes copied, 0.000186665 s, 2.7 MB/s

然后再使用 bochs 运行一下:

7.png

这次就先讲到操作显卡, 下一篇我们将要学习操作硬盘, 并进入 loader