写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

前言

  之前我们搭建好了Bochs学习环境(没搭好的回去弄好再回来看),可惜没有合法的启动盘,那么什么是启动盘,如何正确的启动,下面我们来开始介绍基础部分。

BIOS

  BIOS全称叫Base Input & Output System,即基本输入输出系统。它的主要工作是检测、初始化硬件。

实模式下的 1MB 内存布局

  Intel 8086有20条地址线,故其可以访问1MB的内存空间。若按十六进制来表示,是0x00000 - 0xFFFFF。这lMB的内存空间被分成多个部分。如下表格所示:

  内存地址0x00000 - 0x9FFFF的空间范围是64KB,这片地址对应到了动态随机访问内存DRAM,也就是插在

上的内存条;而0xF0000 - 0xFFFFF64KB内存是ROM,是只读的,存的就是BIOS的代码。硬件自己提供了一些初始化的功能调用,BIOS可以直接调用,并建立了中断向量表,就可以通过int 中断号来实现相关的硬件调用。而这些中断只有重要的、保证计算机能运行的那些硬件的基本IO操作,不像高级语言有各种花里胡哨的功能。

  我们还要说明一个问题:在CPU眼里,我们插在主板上的物理内存不是它眼里“全部的内存”。这个是由地址总线宽度决定了可以访问的内存空间大小。打个比方,比如小孩学数苹果数目。结果他只会100以内的,如果苹果数目超了100,就不会数了,不认识了。物理内存也是如此,再多的内存,只要识别能力不够,也是浪费。

BIOS 启动

  BIOS是计算机上第一个运行的软件,所以它不可能自己加载自己,由此可以知道,它是由硬件加载的。BIOS代码所做的工作也是一成不变的,而且在正常情况下,其本身一般是不需要修改的,存储在ROM中。ROM也是块内存,内存就需要被访问。而ROM被映射在0xF0000 - 0xFFFFF处,只要访问此处的地址便是访问了BIOS,这个映射是由硬件完成的。如果不太理解,可以学一下单片机的基础知识和电工学下册。

  BIOS本身是个程序,程序要执行,就要有个入口地址才行,此入口地址便是0xFFFF0

  当我们开始启动虚拟机进入调试状态时,你会看到如下内容:

  可以看到,ip指向的地址指令是jmp far f000:e05b,这个是跨段跳转,最后执行结果是到了0xFE05B这个地址,这个是真正BIOS代码开始的地方。

  接下来BIOS便马不停蹄地检测内存、显卡等外设信息,当检测通过,并初始化好硬件后,开始在内存中0x000-Ox3FF处建中断向量表IVT并填写中断例程。然后它的任务完成了,剩下的部分就是交给下一个“负责人”继续处理。

0x7c00 杂谈

  BIOS最后一项工作校验启动盘中位于0盘0道1扇区的内容。在计算机中是习惯以0作为起始索引的,用“相对”的概念,即偏移量来表示位置显得很直观,所以很多指令中的操作数都是用偏移表示的。0盘0道1扇区本质上就相当于0盘0道0扇区。为什么称为1扇区呢?因为硬盘扇区的表示法有两种,我们描述0盘0道1扇区用的便是其中的一种:CHS方法,即柱面Cylinder、磁头Header、扇区Sector;另外一种是LBA方式,这里救不说了。0盘说的是0磁头,因为1张盘是有上下两个盘面的,1个盘面上对应一个磁头,所以用磁头Header来表示盘面。0道是指0柱面,柱面Cylinder指的是所有盘面上、编号相同的磁道的集合,形象一点描述就是把很多环叠摞在一起的样子,组合在之后是1个立体的管状。1扇区是将磁道等距划分成一段段的小区间,由于磁道是圆形的,确切地说是圆环,这些被划分出来的小区间便是扇形,所以称为扇区,而在CHS方式中扇区的编号是从1开始的

  如果此扇区末尾的两个字节分别是魔数0x55OxAABIOS便认为此扇区中确实存在可执行的程序,此程序便是主引导记录MBR,它会被加载到物理地址0x7c00,随后跳转到此地址,继续执行。反之,它就不认。

  BIOS跳转到Ox7c00是用jmp 0:Ox7c00实现的,此时段寄存器cs会被替换成0

为什么 MBR 住在这里

  因为近啊。就好比你会把经常用的放到身边,用到就会直接拿出来,如果把它放到老远的位置,这个不就费劲了吗。对于BIOS来说,MBR就是经常用的东西,放到身边才方便。

为什么是 0x7c00 地址

  据说是历史原因,BIOS规范。它最早出现在IBM公司出产的个人电PC5150 ROM BIOSINT 19H中断处理程序中。

  MBR不是随便放在哪里都行的,首先不能覆盖己有的数据,其次,不能过早地被其他数据覆盖。通常MBR的任务是加载某个程序(这个程序一般是内核加载器,很少有直接加载内核的)到指定位置,并将控制权交给它。

  按DOS 1.0要求的最小内存32KB来说,MBR希望给人家尽可能多的预留空间,这样也是保全自己的作法,免得过早被覆盖,所以MBR只能放在32KB的末尾。其次,MBR本身也是程序,是程序就要用到栈,栈也是在内存中的,虽然本身只有512字节,但还要为其所用的栈分配点空间,所以其实际所用的内存空间要大于512字节,估计1KB内存够用了。

  综上,选择32KB中的最后1KB最为合适,那此地址是多少呢?32KB换算为十六进制为0x8000,减去1KB(0x400)的话,等于0x7c00

MBR 杂谈

  MBR是独立于操作系统的,能够直接在裸机上运行。它的大小必须是512字节,保证0x550xAA这两个

魔数恰好出现在该扇区的最后两个字节处。下面我们来编写一个MBR程序,并让它跑起来。

  我们本教程使用的16位汇编器是as86,也是Linux 0.11编写启动代码的其中一个汇编器。它的汇编语法类似Intel的,而不是麻烦的AT&T,具体用法请在终端输入man as86查看。

  现在as86并不自带,我们需要安装,在终端输入以下指令:

sudo apt install bin86

  安装成功后,如果输入as86显示如下信息,表示安装成功:

as: usage: as [-03agjuwO] [-b [bin]] [-lm [list]] [-n name] [-o obj] [-s sym] src

  从头啥也不会开始写也不现实,我给出一个以供参考:

.globl begtext,begdata,begbss,endtext,enddata,endbss ;全局标识符,供 ld86 链接使用。
.text
begtext:
.data
begdata:
.bss
begbss:
.text BOOTSEC=0x7C0 entry start
start:
jmpi go,BOOTSEC ;段间跳转 BOOTSEC 指出跳转地址,标号go是偏移地址
go:
mov ax,cs
mov ds,ax
mov es,ax
mov cx,#20 ;共显示20个字符
mov dx,#0x1004 ;字符显示在屏幕第17行,第5列处
mov bx,#0x000c ;字符显示属性为红色
mov bp,#msg ;指向要显示的字符
mov ax,#0x1301 ;写字符串并移动光标到串结尾处
int 0x10
loop0: jmp loop0 ;死循环
msg:
.ascii "Loading system...!"
.byte 13,10
.org 510 ;表示以后语句从地址 510 偏移开始存放
.word 0xAA55 ;有效引导扇区标志,提BIOS加载引导扇区
.text
endtext:
.data
enddata:
.bss
endbss:

  这个代码我命名为boot.s,然后在该代码所在文件夹下进入终端,输入以下指令:

as86 -0 -a -o boot.o boot.s
ld86 -0 -d -o boot.bin boot.o

编译参数

  这样得到的就是我们想要的内容文件boot.bin。不过我们得把这几个命令行参数介绍一下。

-0

  as86是生成16位汇编代码,如果用了超过8086指令集发出警告。

  ld86是生成16位文件头,这个我们不要,需要删除。

-a

  启用与Minix asld的部分兼容性,看不懂可以不管。

-d

  删除文件头。

-o

  输出文件名/路径。

写入镜像

  得到boot.bin之后,我们需要将这个数据写入虚拟镜像test.img当中,我们需要输入以下命令:

dd if=boot.bin of=test.img bs=512 count=1 conv=notrunc

  dd是用于磁盘操作的命令,可以深入磁盘的任何一个扇区。如果要了解详情,请在终端输入man dd。这里我们仅仅介绍我们使用的参数。

if=

  指定要读取的文件。

of=

  指定把数据输出到哪个文件。

bs=

  指定块的大小,dd是以块为单位来进行IO操作的,得指明块是多大字节。

count=

  指定拷贝的块数。

conv=

  指定如何转换文件。建议在追加数据时,conv最好用notrunc方式,也就是不打断文件。

测试

  执行完这些操作后,我们双击我们的startLearning.sh看看结果:

  这就说明成功了。

编写 Makefile

  以后我们如果频繁更改编译,每次输入这几个指令是不是太麻烦了?我们可以写一个Makefile文件,每次只需在该目录下输入make就可以重新编译:


all: boot.bin img boot.bin: boot.s
as86 -0 -a -o boot.o boot.s
ld86 -0 -d -o boot.bin boot.o img: boot.bin
rm -f test.img
bximage -hd -mode="flat" -size=60 -q test.img
dd if=boot.bin of=test.img bs=512 count=1 conv=notrunc clean:
rm -f boot.bin boot.o test.img

磁盘读写

  有关磁盘结构,这里就不多说了,我们把重点放到如何用汇编来读写磁盘相关内容。如果想详细了解建议看《操作系统真相还原》的第134页,或者从网络找相关资料。

  硬盘控制器属于IO接口,CPU和硬盘打交道是通过硬盘控制器实现的,开始硬盘和控制器是分开的,后来被整到一起,这种接口便称为集成设备电路( Integrated Drive Electronics, IDE )。

  让硬盘工作,我们需要通过读写硬盘控制器的端口,端口的概念在此重复下,端口就是位于IO制器上的寄存器,此处的端口是指硬盘控制器上的寄存器。但硬盘十分复杂,目前我们只用到其中的一小部分,具体了解详情请自行搜索AT Attachment with Packet Interface,一共三卷。

  端口可以被分为两组,Command Block registersControl Block registersCommand Block registers用于向硬盘驱动器写入命令宇或者从硬盘控制器获得硬盘状态,Control Block registers用于控制硬盘工作

状态。在Control Block registers组中的寄存器已经精减了,而且咱们基本上用不到,就不赘述了,下面重点介绍Command Block registers组中的寄存器。

  端口是按照通道给出的,也就是说,端口不是直接针对某块硬盘的。一个通道上的主、从两块硬盘都用这些端口号,要想操作某通道上的某块硬盘,需要单独指定。

  Data寄存器在名字上我们就知道它是负责管理数据的,它相当于数据的门,数据能进,也能出,所以其作用是读取或写入数据。这个寄存器是16位的,得到了特殊照顾。在读硬盘时,硬盘准备好的数据后,硬盘控制器将其放在内部的缓冲区中,不断读此寄存器便是读出缓冲区中的全部数据。在写硬盘时,我们要把数据源源不断地输送到此端口,数据便被存入缓冲区里,硬盘控制器发现这个缓冲区中有数据了,便将此处的数据写入相应的扇区中。

  读硬盘时,端口0x1710x1F1的寄存器名字叫Error寄存器,只在读取硬盘失败时有用,里面才会记录失败的信息,尚未读取的扇区数在Sector count寄存器中。在写硬盘时,此寄存器有了别的用途,被称之为Feature寄存器。有些命令需要指定额外参数,这些参数就写在Feature寄存器中。寄存器都是8位宽度。

  Sector count寄存器用来指定待读取或待写入的扇区数。硬盘每完成一个扇区,就会将此寄存器的值减一,所以如果中间失败了,此寄存器中的值便是尚未完成的扇区。这是8位寄存器,最大值为255,若指定为0,则表示要操作256个扇区。

  硬盘中的扇区在物理上是用柱面-磁头-扇区来定位的Cylinder Head Sector,简称为CHS,但每次我们要事先算出扇区是在哪个盘面,哪个柱面上,这太麻烦了,但这对于磁头来说很直观,它就是根据这些信息来定位扇区的。我们希望磁盘中扇区从

0开始依次递增编号,不用考虑扇区所在的物理结构,这是一种逻辑上为扇区址的方法,全称为逻辑块地址Logical Block Address

  LBA有两种,一种是LBA28,用28位比特来描述一个扇区的地址,最大支持128 GB,为了简单,我们可以使用该方式;另外一种是LBA48,用48位比特来描述一个扇区的地址,最大支持131072 TB,目前没有任何存储器超过该大小。

  介绍完了LBA,现在可以说LBA寄存器了,这里有LBA lowLBA midLBA high三个,它们三个都是8位宽度的。LBA low寄存器用来存储0-7位,LBA mid寄存器用来存储8-15位,LBA high寄存器存储16-23位。但这总共才24位,连LBA28都不够,咱们怎么用呢?Device寄存器。

  Device寄存器是个杂项,它的宽度是8位。在此寄存器的低4位用来存储LBA地址的24-27位。索引4位用来指定通道上的主盘或从盘,0代表主盘,1代表从盘。索引5位用来设置是否启用LBA方式,1代表启用LBA模式,0代表启用CHS模式。剩余的两位,称为MBS位,都固定是1

  在读硬盘时,端口0x1F70x177的寄存器名称是Status,它是8位宽度的寄存器,用来给出硬盘的状态信息。索引0位是ERR位,如果此位为1,表示命令出错了,具体原因可见Error寄存器。索引3位是Request位,如果此位为1,表示硬盘己经把数据准备好了,主机现在可以把数据读出来。索引6位是 DRDY,表示硬盘就绪,此位是在对硬盘诊断时用的,表示硬盘检测正常,可以继续执行一些命令。索引7位是BSY位,表示硬盘是否繁忙,如果为1表示硬盘正忙着,此寄存器中的其他位都无效。剩余的几位用不到暂且不关注。

  在写硬盘时,端口0x1F70x177的寄存器名称是Command。此寄存器用来存储让硬盘执行的命令,只要把命令写进此寄存器,硬盘就开始工作了。在咱们的系统中,主要使用了三个命令。

  1. identify: 0xEC ,即硬盘识别。
  2. read sector: 0x20 ,即读扇区。
  3. write sector: 0x30 ,即写扇区。

  我们来用图简单总结一下:

  不管是读硬盘,还是写硬盘,都不是一个指令就完事的。我们先理顺一个步骤:

  1. 先选择通道,往该通道的Sector count寄存器中写入待操作的扇区数。
  2. 往该通道上的三个LBA寄存器写入扇区起始地址的低24位。
  3. Device寄存器中写入LBA地址的24-27位,并置第6位置为1,使其为LBA模式,设置第4位,选择操作的硬盘(master硬盘或slave硬盘)。
  4. 往该通道上的Command寄存器写入操作命令。
  5. 读取该通道上的Status寄存器,判断硬盘工作是否完成。
  6. 如果以上步骤是读硬盘,进入下一个步骤。否则,结束。
  7. 将硬盘数据读出。

  硬盘工作完成后,它己经准备好了数据,咱们该怎么获取呢?一般常用的数据传送方式如下:

  1. 无条件传送方式
  2. 查询传送方式
  3. 中断传送方式
  4. 直接存储器存取方式DMA
  5. IO 处理机传送方式

  这些传送方式我就不细说了,这不是我们的重点。感兴趣可以翻阅《操作系统真相还原》的第139页,或者其他资料。第1种方法不能用,因为硬盘需要在某种条件下才能传输。第4种和第5种需要单独的硬件支持。所以我们实现会使用较为简单的第2种和第3种。

  在之后的章节,弄好保护模式和分页的基础,我们会使用所有已学知识,学习Linux 1.1内核源码,并仿照逐步完善写一个十分简单的内核。

实模式杂谈

  弄了这么多,我们需要复习一下实模式相关的知识,因为这几篇之后,我们就要搞保护模式,会花费大量的篇幅介绍基础知识。

  在实模式下CPU访问数据将按照基址 + 偏移来进行。至于分类有寄存器寻址、直接寻址、内存寻址。

  在该模式下,用户程序和操作系统可以说是同一特权的程序,因为实模式下没有特权级,它处处和操作系统平起平坐,所以可以执行一些具有破坏性的指令。程序可以随意修改自己的段基址,这样便在内存空间内不受阻拦,可以随意访问任意物理内存,包括访问操作系统所在的内存数据,完全没有保护性可言。用户程序甚至可以覆盖操作系统在内存中的映像,整个计算机世界的和平全靠程序员的心情。

  俗话说得好,光说不练假把式,如下是本节相关的练习。如果练习没做成功,就不要看下一节教程了。

练习与思考

  1. 独立完成本篇实验,并成功在Bochs中打印出红色的Loading system...!字符串。

下一篇

  羽夏看Linux内核——段相关入门知识

羽夏看Linux内核——启动那些事的更多相关文章

  1. 羽夏看Linux内核——环境搭建

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作.如想转载,请把我的转载信息附在文章后面,并 ...

  2. 羽夏看Linux内核——引导启动(上)

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作.如想转载,请把我的转载信息附在文章后面,并 ...

  3. 羽夏看Linux内核——引导启动(下)

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作.如想转载,请把我的转载信息附在文章后面,并 ...

  4. 羽夏看Linux内核——中断与分页相关入门知识

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作.如想转载,请把我的转载信息附在文章后面,并 ...

  5. 羽夏看Linux内核——段相关入门知识

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作.如想转载,请把我的转载信息附在文章后面,并 ...

  6. 羽夏看Linux内核——门相关入门知识

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作.如想转载,请把我的转载信息附在文章后面,并 ...

  7. 羽夏看Win系统内核——环境搭建

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  8. 羽夏看Win系统内核——SourceInsight 配置 WRK

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  9. 羽夏看Win系统内核——驱动篇

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

随机推荐

  1. 一文学会Java的交互式编程环境jshell

    什么是交互式编程环境?重点词交互,在这样的编程环境中,你每输入一行代码,环境都会给你一个反馈,这就是交互式的编程环境.这种编程环境并不太适合工程化的复杂性需求,但在一些快速验证.简单计算之类的场景下还 ...

  2. .NET MAUI 正式版GA发布

    .NET MAUI – 一个代码库,多个平台 欢迎使用 .NET 多平台应用 UI.此版本标志着我们统一 .NET 平台的多年旅程中的新里程碑.现在,您和超过 500 万其他 .NET 开发人员拥有了 ...

  3. 前端向后端传递formData类型的二进制文件

    // 获取到的文件file类型转换为formData类型 let formData = new FormData(); formData.append("file", file文件 ...

  4. 【Unity Shader学习笔记】Unity光照基础-高光反射

    1.原理 1.1.Phong模型 计算高光反射需要表面法线.视角方向.光源方向.反射方向等. 在这四个矢量中,我们实际上只需要知道其中3个矢量即可,而第4个矢量(反射方向r)可以通过其他信息计算得到: ...

  5. 人脸识别库 face_recognition

    face_recognition Windows系统环境下安装 默认环境:anaconda的python3.7版本,win10环境 第一步:安装dlib 从网络上下载: http://dlib.net ...

  6. Navicat 连接 MySQL

    目录 简述 新建连接 常见错误 简述 Navicat 是一套快速.可靠和全面的数据库管理工具,专门用于简化数据库管理和降低管理成本.Navicat 图形界面直观,提供简便的管理方法,设计和操作 MyS ...

  7. Eureka高可用集群搭建

    就是搭建Eureka的集群. 每个Eureka Server需要相互注册,确保数据一致. 我这里准备两个Eureka Server  他两的POM文件配置是一样的 <dependencies&g ...

  8. SAP APO-供需匹配

    供需匹配包含主要功能"匹配能力"(CTM)和一个用于分配库存的附加功能. 在高级计划和优化中,SDM组件为这些应用程序提供跨工厂供应策略- 生产计划和详细计划(PP / DS) 供 ...

  9. hadoop集群搭建——单节点(伪分布式)

    1. 准备工作: 前提:需要电脑安装VM,且VM上安装一个Linux系统 注意:本人是在学习完尚学堂视频后,结合自己的理解,在这里做的总结.学习的视频是:大数据. 为了区分是在哪一台机器做的操作,eg ...

  10. sql-删除重复数据-oracle

    删除重复数据 准备重复数据 create table mystudent ( stuno number , stuname varchar2(20), stuage number ); insert ...