mini-os
2020/11/6 刚跟着书测试完了代码,太累了明天后天再整理
2020/11/8 打了学校举办的 acm 比赛,感受到了自己有多菜,老老实实回来写博客了,完成了 Boot 部分
2020/11/9 完成 Loader 部分
2020/11/10 整理出思维导图
2020/11/13 补充内容
自己动手写一个操作系统
参考学习《一个64位操作系统的设计与实现》(田宇著)
前言: bochs 环境配置,可参考同 tag 下的 bochs 那篇。
书上的代码给的很破碎,看书根本实现不出来…,完整代码可以参考这个 github 仓库 The-design-and-implentation-of-a-64-bit-os
自己磨蹭了快一个月才看完一章(第三章),不得不说自己计组是白学了…,第三章的汇编感觉是真的难,涉及到的东西也是最多的
本系列用于记录学习笔记,构建思维导图,以及补充各种书上没有说的知识,希望这个系列结束后自己对操作系统的理解能更上一层楼吧(我自己是肯定写不出来的)
Boot & Loader
Boot
计算机上电之后,首先会经过自检,这个过程 BIOS 会检测硬件设备是否存在问题,如果没有,这时将根据 BIOS 的启动项配置选择引导设备,目前支持软盘启动,U盘启动,硬盘启动以及网络启动
啥是软盘
书上创建的软盘都是 1.44M 的,这里就以 1.44M 为例简述一下软盘的结构:
2个盘面(0和1),一个盘面有80条磁道(或称磁柱),一个磁道有18个扇区,一个扇区大小为512 Byte,于是软盘总容量:2*80*18*512 Byte = 1474560 Byte=1.44M
软盘不以扇区为单位存放数据,而是以多个扇区(2n个)构成的簇(Cluster),一个簇所含的扇区数与盘容量以及 FAT 表格式有关,特别的,2M 以下的磁盘一个簇只有一个扇区,一个文件至少占一个簇。
需要留意,软盘的第0磁头第0磁道第1扇区实际是软盘的第一个扇区。
Boot 引导原理
对于一张 3.5 英寸的 1.44MB 的软盘来讲,一个扇区的容量只有 512B,而 BIOS 仅仅只负责加载这一个扇区到物理内存中,这样小的空间肯定容不下操作系统,Boot 程序仅仅只能作为一个一级助推器,将更加强大的 Loader 装载到内存中,这也可以看做是硬件设备向软件移交控制权。
那么怎么让 BIOS 读取到我们的 Boot 呢,实际上这和 BIOS 的自检有关。BIOS 自检结束后会根据启动选项设置去选择启动设备,即检测软盘的第0磁头第0磁道第1扇区,是否以数值 0x55 和 0xaa 两字节作为结尾,如果是,那么 BIOS 认为这个扇区是一个 BootSector(引导扇区),进而把此扇区的数据复制到物理内存地址 0x7c00 处然后跳到 0x7c00 去执行这段程序。
Boot 做的事情很简单:加载 Loader 到内存,具体一点来说就是,从根目录中读入一个扇区的数据,接着遍历这个扇区的目录项,寻找与 (LOADER BIN,0) 相匹配的目录项,接着把文件内的数据和代码读入内存,然后跳转过去。
FAT12 文件系统简述
Boot 里面最关键的代码都与你的文件系统有关系,由于我们使用的是上个世纪的软盘,文件系统是过时的 FAT12,但也需要了解一下这种文件系统,同时它也是 FAT32 的前身(应该)。
下面的内容主要参考这篇博客 https://zhuanlan.zhihu.com/p/121807427,这里提取重要的部分:
FAT12 文件系统将2880个扇区分成5个部分:MBR 引导记录,FAT1表,FAT2表,根目录,数据区
部分 | 长度 |
---|---|
MBR | 1个扇区 |
FAT1 | 9个扇区 |
FAT2 | 9个扇区 |
根目录 | 14个扇区 |
数据区 | 2847个扇区 |
MBR引导
记录 FAT12 文件系统整个组织结构的信息,这些信息描述了 FAT12 文件系统对磁盘扇区的管理情况。
FAT1,FAT2
这两个表完全相同,FAT2 目的是为了修复 FAT1 表。因此在实际操作的过程中可以将 FAT1 表在关机的时候赋值给 FAT2 即可。
FAT 表每12个bit(1.5个字节)为一个FAT项,代表数据区的一个簇。第0个和第1个FAT项始终不使用,对应 0xFF0(坏簇标记) 和 0xFFF(簇结束标记),第2个FAT项开始表示数据区的每一个簇,也就是说,第2个FAT项表示数据区第一个簇,即2号簇代表32号扇区。依次类推。由于文件系统将无法确保文件中的数据存储在连续的磁盘扇区内,则借助 FAT 表项,将这些不连续的文件片段按照簇号连接起来。簇和扇区的构成看起来就像C语言中的链表:
1 | struct list |
每个簇具储存了什么呢?首先12个二进制数字表示这个簇指向的下一个簇。FAT 表从零开始编号。如果2号簇储存的数字为3,那么说明2号簇指向3号簇。3号簇的12位数字储存的是5的话,那么说明3号簇指向5号簇。这就形成了一个链表,链表的空指针 NULL(结尾标志),使用 0xFFF 表示。
为什么簇和扇区要分离? 我们当然可以把 next 指针放在一个扇区内,这样看上去更像编程中的链表结构。然而在实际操作系统中,很多时候只需要访问簇号就可以了,不需要访问扇区中的数据(计算目录的大小),这个时候只读取一个扇区内的容基本上就可以完成操作,极大的减少了 I/O 时间。
根目录区
根目录顾名思义就是系统最初的文件夹。根目录中储存了文件和子目录,两者的真正数据都是储存在数据区中,在根目录中之储存基本信息,用来找到它们真正的数据。这些基本信息叫文件目录项。
一个文件目录项有32个字节,结构如下:
偏移量 | 长度 | 描述 |
---|---|---|
0 | 8 | 文件名 |
8 | 3 | 文件扩展名 |
11 | 1 | 文件属性 |
12 | 10 | 保留位 |
22 | 2 | 创建时间 |
24 | 2 | 创建日期 |
26 | 2 | 首簇号 |
28 | 4 | 文件大小 |
- 如果文件名没有满8位,则需要将后面的字符置为空格,而不是0
- 文件有3个属性:隐藏文件0x27,目录0x10,普通文件0x20
- 首簇号是文件第一个段信息存放的地址,FAT12 中,一个簇映射到一个数据区的扇区。
数据区
数据区的某一个扇区如果是一个目录,那么这个目录被称为子目录。子目录分为一级、二级…,他们的组织方法和根目录基本一样。子目录中一定要包含 .
和 ..
项。即 dot 和 dotdot 项。
Loader
Loader 必须要在内核程序执行前为其准备好一切数据,比如说硬件的检测信息,处理器模式切换,向内核传递参数等。
-
Loader 引导加载程序需要检测的硬件信息很多,主要是通过 BIOS 中断服务程序来获取和检测硬件信息。由于 BIOS 在上电自检出的大部分信息只能在实模式下获取,而内核运行在非实模式下,那么就必须在进入内核程序前将这些信息检测出来。
-
处理器模式切换在后面介绍。
-
Loader 向内核传递数据分为两类:控制信息和硬件数据信息,这些数据一方面控制内核程序的执行流程,另一方面为内核初始化提供数据信息支持。
- 控制信息一般用于控制内核执行流程或者限制内核的某些功能,如启动模式(图形 or 命令行)等。
- 硬件数据信息通常供内核程序在初始化的时候分析,配置和使用,如内存地址等。
A20 功能
参考文档:http://wenku.baidu.com/view/b673e3360b4c2e3f57276369.html
在 8086/8088中,只有 20 根地址总线,所以可以访问的地址是 ,但由于 8086/8088 是 16 位地址模式,能够表示的地址范围是 0-64K,所以为了在 8086/8088 下能够访问 1M 内存,采用了segment:offset
的寻址方法,实际地址等于 segment << 4 + offset
,这样 8086/8088 能寻址的范围就成了从 0000:0000 ~ 0000:FFFF
开始到 F000:0000 ~ F000:FFFF
但这种方式引起了新的问题,通过上述分段模式,能够表示的最大内存为:FFFFh:FFFFh=FFFF0h+FFFFh=10FFEFh=1M+64K-16Bytes(1M多余出来的部分被称做高端内存区HMA)
。但 8086/8088 只有 20 位地址线,如果访问 100000h~10FFEFh 之间的内存,系统并不认为其访问越界而产生异常,而是自动从重新0开始计算,也就是说系统计算实际地址的时候是按照对1M求模的方式进行的,这种技术被称为 wrap-around。
到了80286,系统的地址总线发展为24根,这样能够访问的内存可以达到。就可以正常的访问到 100000H-10FFEFH
之间的内存,而不是象过去一样重新从0开始。这样就造成了访问高端内存时 80286 在 real mode 下和 8086/8088 行为不同,兼容性就存在问题了。
为了解决上述问题,IBM 想出了一个古怪的方法:当 80286 运行在 real mode 时,将 A20 地址线(第 21 条 address bus)置为 0。方法是使用键盘控制器上剩余的一些输出线来管理第 21 根地址线(从0开始数是第20根),被称为 A20Gate:如果A20 Gate 被打开,则当程序员给出 100000H-10FFEFH 之间的地址的时候,系统将真正访问这块内存区域;如果 A20Gate 被禁止,则当程序员给出 100000H-10FFEFH 之间的地址的时候,系统仍然使用 8086/8088 的方式(认为造成了 wraparound 现象)。
上面所述的内存访问模式都是实模式,在 80286 以及更高系列的 PC 中,即使 A20Gate 被打开,在实模式下所能够访问的内存最大也只能为 10FFEFH,尽管它们的地址总线所能够访问的能力都大大超过这个限制。为了能够访问 10FFEFH 以上的内存,则必须进入保护模式。(其实所谓的实模式,就是 8086/8088 的模式,这种模式存在的唯一理由就是为了让旧的程序能够继续正常的运行在新的 PC 体系上)
在这里,我们开启实模式也是为了让 FS 段寄存器在实模式下寻址能力超过 1MB,这样就可以打开 Big Real Mode 模式。
处理器模式
这个不得不说一下历史:Intel 系列的微处理器主要发展过程是:8080,8086/8088,80186,80286,80386,80486,Pentium…
从 80386 开始,cpu 就有了3种工作模式:
- 实模式
- 保护模式
- 虚拟 8086 模式
在刚刚启动的时候是实模式,等到操作系统运行起来的时候就是保护模式。实模式其实就是一个 8086 模式,只能寻址 1M 以下的常规内存,而在保护模式下,32条地址线全部有效,可以寻址高达 4G 的物理地址空间。而虚拟 8086 就是在保护模式下运行的实模式,主要用于在 32 位保护模式下兼容执行纯 16 位程序,其实还是属于保护模式。
保护模式比实模式多了对内存的保护,程序内部使用的都是操作系统提供的虚拟地址,而由操作系统将虚拟地址转化为物理地址。这样就避免了实模式下用户程序的指针修改其他程序的物理地址的值造成的错误结果。
上面都是历史了,现在还有一种扩展模式:IA-32e:
- 由于保护模式加入了权限和分页等管理功能,使得程序的执行效率降低。当页管理单元出现后,段机制显得更加多余,随着硬件不断提升和对大容量内存的渴望,就出现了 IA-32e 模式,它简化了段级保护措施,升级内存的寻址能力,扩展了页管理单元的组织结构和页面大小。
- IA-32e 有兼容模式和 64 位模式,子模式的切换完全基于代码段寄存器。64位模式可以执行 64 位程序,但要运行 16 或者 32 位程序,就需要切换到兼容模式了。
最后记一下 bochs 调试命令大全 https://blog.csdn.net/hua19880705/article/details/8124514