前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >计算机是如何启动的?一文教你自制操作系统

计算机是如何启动的?一文教你自制操作系统

作者头像
用户3147702
发布2022-06-27 14:19:04
2.8K0
发布2022-06-27 14:19:04
举报
文章被收录于专栏:小脑斧科技博客

1. 引言

你是否也和我一样,想要知道当我们轻轻按下电源键,电脑哔的一声响,几行字闪过,然后操作系统的启动画面出现,电脑启动可以被使用,这一系列过程中,电脑到底做了什么呢?

2. boot — 计算机启动的悖论

如果你深入的思考过计算机应该如何被启动,你就会发现这其中存在着一个悖论 — 如果要启动计算机,那么就要先执行启动程序,可是如果计算机没有启动,那么就没有办法去执行启动程序西方有个谚语:

pull oneself up by one’s bootstrap 拽着自己的鞋带把自己拉起来

这就是计算机的启动过程被称为 boot 的原因,他就来源于上述谚语中的单词“bootstrap”,那么,计算机设计中是如何解决这个悖论的呢? 早期计算机通过先为内存供电,将启动所需的程序预先写入内存的临时方法来解决这个悖论,但后来,BIOS 的诞生终于圆满的解决了这个问题。

3. BIOS

上世纪七十年代初,只读存储器 ROM 诞生了,他不再会因为掉电而造成数据的丢失,数据一旦烧录,就无法更改。 于是,只要在计算机出厂时,将固定的程序写入 ROM,并且设置电脑开机时率先读取 ROM 的固定位置并执行,就可以解决上述的悖论了。 这个在计算机只读存储区 ROM 中存储的就是 BIOS 程序(Basic Input Output System) BIOS 程序主要做了下面的两件事:

3.1. 硬件自检与初始化

硬件自检也称为 POST(Power On Self Test) 主要功能就是检测硬件工作是否正常,是否满足最基本的启动需要,并且对这些硬件进行必要的初始化工作。 早期的计算机会在这一过程中显示下面的界面:

但随着计算机工业的发展,硬件问题发生的概率越来越低,与此同时,计算机的启动速度越来越快,整个硬件自检过程也在1秒内就可以完成,这个界面也就不再显示了,除非出现了什么问题,屏幕上才会显示出对应的错误描述信息。

3.2. 定义启动顺序

对硬件进行一系列检测与必要的初始化工作后,BIOS 会去遍历用户配置的引导设备列表,也就是我们定义的设备的先后启动顺序。 BIOS 从这些设备中一个一个地去尝试启动,直到找到符合要求的设备。 那么,这里说到的“符合要求”是什么呢?怎么确定一个设备是否是启动设备呢? IBM 制定了一个规范,那就是如果一个存储设备的第 511 字节和第 512 字节分别存储的是 0xAAh 与 0x55h,那么,BIOS 就认为这个设备是启动设备,从而不再去寻找下一个设备,因为磁盘每个扇区正好是 512 字节,这第一个 512 字节也就因此被称为引导扇区。 此时,BIOS 将这第一个扇区载入到内存地址 0x7C00h 的位置,就开始执行这段引导代码了,这也就是操作系统设计时的第一段代码,通过这段代码会加载并跳转到磁盘的另一段代码中,从而开始整个操作系统的引导、初始化与启动工作。

4. BIOS 中断

既然我们已经知道了计算机启动的上述过程,我们能不能编写自己的启动程序呢?答案当然是肯定的。 那就让我们来编写操作系统的第一步,从我们自己的启动盘启动,并在屏幕上显示“Hello World my OS!”。

4.1. BIOS 中断基本介绍及列表

既然我们要在屏幕上显示“Hello World my OS!”,那么首先要解决的问题是怎么让 BIOS 能够将内存中的信息显示在屏幕上。 BIOS 通过硬件中断的方式内置了很多 IO 功能,具体可以参考:https://en.wikipedia.org/wiki/BIOS\_interrupt\_call,也可以参考本文的附录。 其中 10H 号中断就是显示服务,我们通过 INT 10H 指令就可以触发 10H 号中断。 在中断触发后,BIOS 会去读取寄存器 AH 中的值,并根据这个字节的内容,来进行不同的操作,例如,如果 AH 中存储的是 13H,BIOS 就会在屏幕上显示一行字符串。

4.2. 利用 BIOS 10H 号中断实现字符串显示

上面已经提到,在 INT 10H 触发时,如果 AH 中存储的是 13H,那么 BIOS 就会在屏幕上显示一行字符串。

4.2.1. 显示方式

寄存器 AL 中的最低两位,决定了具体的显示方式。

  • 0 — 目标字符串仅仅包含字符,显示属性在寄存器 BL 中,不移动光标
  • 1 — 目标字符串仅仅包含字符,显示属性在寄存器 BL 中,移动光标
  • 2 — 目标字符串包含字符和属性,不移动光标
  • 3 — 目标字符串包含字符和属性,移动光标

4.2.2. 显示属性

如上所述,当 AL 的 1 位为 0 时,BL 表示显示属性:

  • BIT2 ~ BIT0 — 前景色,RGB 值,000b 为黑色,111b 为白色
  • BIT3 — 前景色是否加亮,为 1 加亮,为 0 不加亮
  • BIT6 ~ BIT4 — 背景色,取值见前景色
  • BIT7 — 是否闪烁,0 不闪烁,1 闪烁

4.2.3. 其他属性

下列寄存器中存储了显示所需的其他信息:

  • ES:BP — 字符串在内存中的段地址与偏移地址
  • CX — 字符串长度
  • BH — 视频区页数
  • DH — 存储在第几行显示
  • DL — 存储在第几列显示

5. 编写我们自己的操作系统 — Hello World my OS!

通过上面这么多的讲解,我们知道,只需要在第一个扇区的 511 字节和 512 字节设置结束标志:0xAA55h,我们就可以将这个磁盘设置为启动盘,而剩下的 510 字节足够保存我们要在屏幕上显示的字符串了。

5.1. 汇编器的选择

所以我们需要编写一段汇编代码,主流的汇编器主要有四个:微软家的 MASM、Borland 公司的 TASM、开源的 NASM 以及 GNU 汇编器。 MASM 与 TASM 的语法是最为接近的,NASM 语法与他们有一些差别,但只要熟悉三者中一个的语法,通过查阅手册就可以清楚另外两者的代码如何编写了。 推荐是在 windows 平台使用微软家的 MASM,在 linux 平台使用 NASM,网上资料非常多,选择跨平台的 TASM 也可以,至于 GNU 汇编器,他的语法与其他三者的差距最大,除非是非常熟悉 GNU 汇编语法,否则不是太推荐使用。 本文我们选用开源的 NASM 在 linux 环境下进行编写。

5.2. 编写我们自己的启动代码

代码语言:javascript
复制
; author: techlog
    org 07c00h                    ; 将下列程序加载到内存地址 7c00h 处
    ; 初始化段寄存器
    mov ax, cs
    mov ds, ax
    mov es, ax
    call    DisplayString
    jmp $                         ; 跳转到当前位置,无限循环

DisplayString:
    mov ax, BootMessage
    mov bp, ax                    ; ES:BP = 串地址
    mov cx, [MessageLength]       ; CX = 串长度
    mov ax, 01301h                ; AH = 13h 显示字符串,  AL = 01h,显示属性存储在 BL 中
    mov bx, 008ch                 ; BH = 0 从第 0 行开始显示,BL = 8Ch 黑底红字高亮闪烁
    mov dl, 0                     ; 从第 0 列开始显示
    int 10h                       ; 10h 号中断
    ret

BootMessage:    db  "Hello World my OS!"
MessageLength:  db $-BootMessage
times   510-($-$$)  db  0         ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw  0xaa55                        ; 引导扇区标志

5.3. 代码说明

上面代码中的注释已经非常清晰,这里做一些额外的说明。

5.3.1. 获取变量地址

在 DisplayString 函数中,我们看到一个赋值语句:

代码语言:javascript
复制
mov ax, BootMessage

在 MASM 中,我们需要这样写:

代码语言:javascript
复制
mov ax, word ptr BootMessage

MASM 中,如果要取变量的首地址,需要使用 OFFSET 或 PTR 指令,但在 NASM 中并没有这两个指令,取而代之的是,只要是变量,默认都是返回地址,所以直接使用命令 mov ax, BootMessage 就是讲变量 BootMessage 的首地址放入 ax。 而如果你想要将 MessageLength 变量的值放入 cx 中,那么你需要执行:

代码语言:javascript
复制
mov cx, [MessageLength]

方括号表示取该变量的值。

5.3.2. $ 与 $$

标识符也同样代表当前代码的起始地址。除此之外,NASM 增加了

标识符,代表当前段的起始地址。所以,

-

5.3.3. times

times 是 NASM 中十分实用的一个伪指令,他有两个操作数:

代码语言:javascript
复制
times n m

表示把 m 重复 n 次。 例如 times 3 db 0 指令相当于:

代码语言:javascript
复制
db 0
db 0
db 0

这有些类似 MASM 中的 DUP 指令(需要先添加 start label 到程序第一行):

代码语言:javascript
复制
db 510-($-start) dup(0)

6. 编译程序 & 写入磁盘

6.1. 编译链接

无论你用哪种汇编器完成代码的编写,都要用相应的汇编器执行编译链接,例如,基于 NASM 编写的上述代码可以在 linux 下执行:

nasm boot.asm -o boot.bin

生成二进制文件 boot.bin,如果提示 nasm 命令不存在,使用对应平台下的包管理机制或到官网下载源码编译安装即可。

6.2. 写入磁盘

6.2.1. linux 环境

既然我们已经拥有了用于启动的二进制文件,只要将他写入磁盘的第一个扇区并将该磁盘设置为启动盘,开机启动就可以进入这个扇区了。 那么,第一步,我们要写入磁盘。 在 linux 下,可以通过 dd 命令写入:

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

参数 conv=notrunc 意味着a.img不能被截断。

6.2.2. windows 环境

如果你是在 windows 环境下,你也可以使用 rawrite 或者 UltraISO 软件。 如果你安装了 rawrite2 软件,只需要在 cmd 或者是运行窗口中执行:

rawrite2.exe -f boot.bin -d boot.img

同时你也可以安装更为强大的 UltraISO 软件,如下图点击启动菜单,加载引导文件选项:

选择刚刚编译生成的二进制文件,点击工具栏上的保存按钮,就可以生成启动盘 ISO 文件了。

7. 启动你自制的操作系统

你可以将刚刚生成 ISO 或者 IMG 文件刻录到 U 盘、光盘或是软盘上,然后放入计算机,重启,在 BIOS 中设置从该设备启动,就可以看到屏幕上显示出了闪闪的“Hello World my OS!”。 当然,这显得有些繁琐,我们完全可以换一种方式 — 使用虚拟机。 如果你在 linux 环境下,可以使用轻量化又功能强大的 bochs 虚拟机,或者如果你有图形界面或在 windows、mac 环境下,也可以使用 virtualbox、VMware 等等。 这里就不赘述上述某个虚拟机的使用方式了,总之创建一个虚拟环境,然后将我们生成的 img 或是 ISO 文件载入虚拟机的虚拟软驱或光驱,然后点击启动按钮,你就会看到:

上图就是博主在自己的 windows 环境下使用 virtualbox 虚拟机启动的界面。 是不是非常激动人心?是不是从未想过制作一个自己的操作系统是如此简单?

很遗憾,这还完全不能称得上是一个操作系统,但我们已经顺利让 BIOS 从我们的初始扇区启动了,并且显示出了激动人心的 Hello World,接下来的事情还有什么难的呢? 真正的操作系统被引导后,究竟又做了哪些事情呢?敬请期待,博主的下一篇文章。

8. 附录 — BIOS 中断列表

  1. 10H 号中断 — 显示服务
  2. 13H 号中断 — 直接磁盘服务
  3. 14H 号中断 — 串行端口服务
  4. 15H 号中断 — 杂项系统服务
  5. 16H 号中断 — 键盘服务
  6. 17H 号中断 — 并行端口服务
  7. 1AH 号中断 — 时钟服务
  8. 00H 号中断 — “0”作除数
  9. 01H 号中断 — 单步中断
  10. 02H 号中断 — 非屏蔽中断(NMI)
  11. 03H 号中断 — 断点中断
  12. 04H 号中断 — 算术溢出错误
  13. 05H 号中断 — 打印屏幕和BOUND越界
  14. 06H 号中断 — 非法指令错误
  15. 07H 号中断 — 处理器扩展无效
  16. 08H 号中断 — 时钟中断
  17. 09H 号中断 — 键盘输入
  18. 0BH 号中断 — 通信口(COM2:)
  19. 0CH 号中断 — 通信口(COM1:)
  20. 0EH 号中断 — 磁盘驱动器输入/输出
  21. 11H 号中断 — 读取设备配置
  22. 12H 号中断 — 读取常规内存大小(返回值AX为内存容量,以K为单位)
  23. 18H 号中断 — ROM BASIC
  24. 19H 号中断 — 重启动系统
  25. 1BH 号中断 — CTRL+BREAK处理程序
  26. 1CH 号中断 — 用户时钟服务
  27. 1DH 号中断 — 指向显示器参数表指针
  28. 1EH 号中断 — 指向磁盘驱动器参数表指针
  29. 1FH 号中断 — 指向图形字符模式表指针

9. 参考资料

https://en.wikipedia.org/wiki/Booting。 https://en.wikipedia.org/wiki/BIOS\_interrupt\_call。 http://www.360doc.com/content/18/0824/12/44974165\_780828242.shtml。 https://www.cnblogs.com/jiu0821/p/4422464.html。 https://techlog.cn/article/list/10183457。 《Orange’s 一个操作系统的实现》。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小脑斧科技博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引言
  • 2. boot — 计算机启动的悖论
  • 3. BIOS
    • 3.1. 硬件自检与初始化
      • 3.2. 定义启动顺序
      • 4. BIOS 中断
        • 4.1. BIOS 中断基本介绍及列表
          • 4.2. 利用 BIOS 10H 号中断实现字符串显示
            • 4.2.1. 显示方式
            • 4.2.2. 显示属性
            • 4.2.3. 其他属性
        • 5. 编写我们自己的操作系统 — Hello World my OS!
          • 5.1. 汇编器的选择
            • 5.2. 编写我们自己的启动代码
              • 5.3. 代码说明
                • 5.3.1. 获取变量地址
                • 5.3.2. $ 与 $$
                • 5.3.3. times
            • 6. 编译程序 & 写入磁盘
              • 6.1. 编译链接
                • 6.2. 写入磁盘
                  • 6.2.1. linux 环境
                  • 6.2.2. windows 环境
              • 7. 启动你自制的操作系统
              • 8. 附录 — BIOS 中断列表
              • 9. 参考资料
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档