首页 > 其他分享 >一个操作系统的设计与实现——第2章 主引导记录

一个操作系统的设计与实现——第2章 主引导记录

时间:2023-11-12 09:58:05浏览次数:24  
标签:引导 字节 记录 扇区 BIOS 地址 0x7c00 MBR 操作系统

2.1 BIOS

当按下开机键的那一刻,发生了什么呢?

这是一个百废待兴的时刻,所有的硬件设备都刚启动,并没有做好准备,甚至连CPU自己都是。此时,就需要一些外力帮助CPU工作起来。具体来说,在CPU刚启动时,其CS:IP被硬件电路设定为f000:fff0。这个地址并非指向内存,而是指向主板的一个非易失性ROM,其中存放的代码被称为基本输入/输出系统(Basic Input Output System,BIOS)。这段代码是CPU在加电后运行的第一段代码,其目的是初始化并检测各个硬件设备,如显示器、硬盘、键盘等。

BIOS的执行时间是十分短暂的,其做的最后一件事情是:将硬盘的第一扇区,共512字节,加载到内存地址0x7c00处,然后通过jmp 0:0x7c00指令跳转至该处执行。这个扇区中的内容被称为主引导记录(Main Boot Record,MBR)。

BIOS的故事讲完了,但上述描述中有一些细节需要进一步讨论。

2.1.1 实模式

此时的CPU工作在实模式下。所谓实模式,指的是8086这款CPU的工作模式。8086是一款16位的CPU,这意味着其寄存器都是16位的,不过,其地址线却不止16根,而是20根,20根地址线能够达到2的20次方,也就是1M的寻址能力。然而,地址线是多了,寄存器的位数却跟不上,16位的寄存器无法存放20位的地址。于是,8086使用了一个打补丁式的设计:在寻址时,先将段寄存器中存放的地址左移4位,再与偏移地址相加,这样就能强行凑出20位的地址了。

2.1.2 什么叫"硬盘的第一扇区"?

硬盘是由一个或多个圆形的盘堆叠而成的,每个盘上的所有同心圆环构成多个磁道,每个磁道又被分成多个扇区。所以,应该这样描述一个扇区:第x个盘面上的第y个磁道上的第z个扇区。即,用三个数字描述一个扇区。但这种描述方法的缺点显而易见:太麻烦了。于是,人们发明了"逻辑扇区"这一概念,所谓逻辑扇区,就是将整个硬盘视为一个超长数组,其由硬盘中的所有扇区排列形成。从而,只需要一个索引值,就能唯一确定一个扇区了。逻辑扇区由硬盘控制器负责转换,将其对应到某个实际的扇区上。

2.1.3 为什么一个扇区是512字节?

这完全是约定,硬盘的一个扇区就是512字节。

2.1.4 为什么是0x7c00这个地址

这个地址沿袭自IBM的一款计算机中的BIOS,属于一个约定俗成的地址。0x7c00这个数刚好是31K,接近32K这个整数,所以可能的原因是:这个BIOS是为有32K内存的计算机准备的,而主引导记录只是一段512字节的过渡代码,要尽量靠上加载,于是,就选用了31K这个位置。

综上,在计算机刚启动时,首先执行的是BIOS中的代码。BIOS在结束之前会加载并跳转到MBR。所以,MBR是操作系统的起点。

2.2 MBR

想要写一个MBR,就需要满足以下要求:

  1. MBR的大小必须是512字节,不能超过,也不能少,如果少了,就需要凑足512字节
  2. MBR的最后两字节必须依次是0x550xaa,这是MBR与BIOS的约定。只有这么做,BIOS才认为MBR有效,才会加载这个MBR

请看本章代码2/2.1/Mbr.s

第1行,声明一个段。这里的vstart=0x7c00是什么意思呢?这涉及到编译器对汇编代码的编址。在默认情况下,编译器会将段中的所有内容(包括指令和数据)从0开始编址,当使用段中的一个符号时,拿到的便是一个从0开始的偏移地址。但在MBR中,这样做是不对的,不应该从0,而应该从0x7c00开始编址。请看以下代码:

section mbr

A: db 0
B: db 1

mov ax, A  ; 0
mov bx, B  ; 1

在MBR中,上述地址是错误的,这两个字节的正确地址应该是0x7c000x7c01

最朴素的修正方法是这样:

section mbr

A: db 0
B: db 1

mov ax, 0x7c00 + A  ; 0x7c00
mov bx, 0x7c00 + B  ; 0x7c01

这样做虽然是对的,但是很麻烦,需要手动调整所有的地址。能从根本上解决问题的方案是:让编译器不要从0开始编址,而是从0x7c00开始,这就是vstart=0x7c00的作用:

section mbr vstart=0x7c00  ; 现在,从0x7c00开始编址

A: db 0
B: db 1

mov ax, A  ; 0x7c00
mov bx, B  ; 0x7c01

第3行,$是nasm编译器提供的一个特殊标号,用于指代当前行,所以,这一行代码等价于:

this: jmp this

这是一个无限循环,目的是让CPU保持在这一行,不要继续向下执行。现在的目标是让MBR先运行起来,并不做任何事情,所以使用了这一指令。

第5行,times是nasm提供的一个伪指令,其格式为:

times 一个数字 一段代码

times伪指令可将后面这段代码重复多次。这里使用times的目的是将MBR用0填充至510字节,为最后的0x550xaa做准备。填充的字节数使用510 - ($ - $$)进行计算。$已经介绍过了,其指代的是当前行的地址;$$是nasm提供的另一个特殊标号,其指代的是当前段的起始地址,故$ - $$表示当前段的大小,510 - ($ - $$)表示当前段距离510字节还差多少。

第7行,定义了0x550xaa,这是BIOS与MBR的约定。

接下来,请看本章代码2/2.1/Makefile

第2行,编译Mbr.s。可以发现,编译好的Mbr文件确实是512字节。

第3行,将编译好的Mbr文件写入虚拟硬盘。其中,if=Mbr用于指定输入文件;of=c.img用于指定输出文件;seek=0用于指定写入的起始逻辑扇区,由于MBR必须位于第0个逻辑扇区,所以这里需要使用seek=0count=1用于指定写入的扇区数,MBR的扇区数为1;conv=notrunc是固定用法。

现在,运行bochs命令,可以看到虚拟机开始正常运行。

2.3. 文本模式显存

实模式下的1M地址空间并不都是指向物理内存的。具体来说,只有0x0~0x9ffff指向物理内存,剩下的0xa0000~0xfffff指向外部设备。在我们的操作系统中,唯一需要关注的外部设备是文本模式显存。

文本模式显存有一个神奇的功能:这段显存里面有什么,屏幕上就显示什么,而且是实时更新的。具体来说,从0xb8000开始的4000个字节,决定了当前屏幕上显示的内容。bochs使用的是25行,80列的显示模式,每个字符使用两字节,所以一共是4000字节。这两字节中的第一字节为这个字符的ASCII码,第二字节的含义如下:

7 6 5 4 3 2 1 0
含义 是否闪烁 背景红色 背景绿色 背景蓝色 是否高亮 文字红色 文字绿色 文字蓝色

也就是说,如果想要显示一个最普通的白色字符,第二字节应为0x7

请看本章代码2/2.2/Mbr.s

第3~4行,将ds切换到0xb800。实模式下的段寄存器在寻址时会左移四位,这样就得到了0xb8000,这是显存的起始地址。

第6~7行,通过写显存,在屏幕的左上角打印一个6

编译这个新的MBR,并启动bochs,可以看到屏幕的左上角已经打印出了6

标签:引导,字节,记录,扇区,BIOS,地址,0x7c00,MBR,操作系统
From: https://www.cnblogs.com/yingyulou/p/17825511.html

相关文章

  • 一个操作系统的设计与实现——第1章 什么是操作系统
    1.1引言什么是操作系统呢?有些读者可能会像曾经的笔者一样,认为操作系统是"一种图形界面";在学习了Linux操作系统后,认为操作系统也可以是"一种命令行"。而不同种类,不同版本的操作系统,则是"不同的图形界面",或是"不同的命令行语法"。那么,以Linux操作系统为例,读者是否想过这些问题呢......
  • 一个操作系统的设计与实现——第5章 加载内核
    一直以来,我们都在使用汇编语言对MBR编程,但对于操作系统这样的复杂程序来说,使用汇编语言是比较困难的。本章将实现操作系统内核的加载与进入。5.1读硬盘的实现原理操作系统存储于硬盘中,现在需要将其读出至内存。想要读硬盘,就需要依次进行以下操作:设定读取的扇区数设定起始扇......
  • 一个操作系统的设计与实现——第9章 硬盘驱动
    操作系统应当具备读写硬盘的能力。因此,本章将要实现的是硬盘驱动。硬盘驱动由两个函数构成:读硬盘函数与写硬盘函数。9.1读硬盘想要读硬盘,就需要提供以下三个信息:起始扇区号读取的扇区数数据存储的地址需要注意的是:读取的扇区数只能是一个8字节的整数。由于读硬盘需要使......
  • 一个操作系统的设计与实现——第8章 内存管理系统
    计算机上的任何程序,包括操作系统自己,都需要使用内存。因此,操作系统需要实现内存管理系统,以进行内存的分配和回收。在我们的操作系统中,内存管理系统由两部分组成:页分配器与页回收器。本章将实现这两个部分。8.1从虚拟地址到物理地址回顾CPU对内存地址的转换过程:使用段寄存器......
  • 一个操作系统的设计与实现——第7章 中断
    7.1什么是中断中断是一种能够随时打断CPU正常工作的机制。这句话看着挺别扭的,CPU工作的好好的,为什么要"随时打断"它?这是因为,CPU需要为诸多外部设备提供服务,以键盘为例,当键盘上的键被按下时,CPU需要对此做出响应和处理,如果不能及时响应,我们会说:"电脑很卡";如果一直都不能响应,我们会......
  • 一个操作系统的设计与实现——第6章 显卡驱动
    进入内核以后,应该做些什么呢?本章将实现一个最容易看到效果的模块:显卡驱动。6.1什么是驱动驱动这个词听起来很高大上,但实际上很简单,就是硬件的接口函数。在软件工程中,可以使用接口封装和简化设计,硬件也是一样。例如:想要读硬盘,需要很多指令设定好几个端口,然后等待硬盘就绪,最后才......
  • 一个操作系统的设计与实现——第12章 任务(三):3特权级任务
    特权级是保护模式的核心概念之一,但我们的操作系统一直没有引入这个概念。这是因为,特权级只有在3特权级任务存在时才有意义。本章将要实现的是3特权级任务的加载与任务切换。12.1特权级12.1.1特权级的功能特权级(PrivilegeLevel),是保护模式中用于限制任务权限的机制。特权级有4......
  • 一个操作系统的设计与实现——第11章 任务(二):0特权级任务
    上一章中,我们的操作系统已经支持内核共享,这为任务的加载和运行做好了准备。本章将要实现的是0特权级任务的加载与任务切换。11.1任务切换的原理11.1.1协同式与抢占式任务切换如果CPU上只运行着Kernel.c的main函数,那么情况非常简单,只需要不断执行下一条指令即可。然而,如果现......
  • 一个操作系统的设计与实现——第10章 任务(一):共享内核
    一直以来,我们的操作系统在启动后,运行的都是Kernel.c中的main函数。只运行这一个函数是不够的,操作系统应当有能力加载并运行其他程序。从本章开始,将使用四章的篇幅讨论操作系统如何加载并运行任务。这里的任务(Task)与进程(Process)是同义词,在操作系统领域中,任务这个词更为常用,请读者......
  • 一个操作系统的设计与实现——第13章 任务(四):任务回收
    在前面的两章中,我们的操作系统均不支持任务回收,所以任务不能退出。本章将要实现的是任务回收功能。13.1任务回收的原理如果一个任务位于任务队列中,其就会被运行。所以,如果一个任务的运行已经结束,它就应该从任务队列中删除。仅仅将任务从任务队列中删除是不够的,这是因为任务还......