动手写一个现代化的操作系统

本文基于彭东的《操作系统实战45讲》笔记,探讨了从代码到机器运行的过程,包括程序编译、装载执行的原理。接着介绍了如何编写简单的内核,涉及引导汇编、C语言主函数、控制计算机屏幕。深入讨论了操作系统的内核结构、设计,对比了宏内核、微内核架构,并概述了Linux、Darwin、Windows NT内核的架构。详细阐述了CPU的实模式、保护模式和长模式,以及在不同模式下的中断处理。最后,文章介绍了地址转换,特别是虚拟地址到物理地址的转换,以及MMU的工作机制。
摘要由CSDN通过智能技术生成

操作系统

前言

  1. 本篇博客是基于彭东的操作系统实战45讲的内容做的学习笔记,目的在于总结和排坑
  2. 专栏链接:https://time.geekbang.org/column/intro/411
  3. 环境是运行在VM上的Ubuntu20
  4. 扫码购买有优惠哦
    在这里插入图片描述

一.程序的运行过程:从代码到机器运行

#include "stdio.h"
int main(int argc, char const *argv[])
{
   
  printf("Hello World!\n");
  return 0;
}

这段代码是每个学习C语言人的第一行代码,让我们学习一下为什么能在屏幕上输出HelloWord,它底层的原理是什么?

计算机硬件是无法直接运行这个 C 语言文本程序代码的,需要 C 语言编译器,把这个代码编译成具体硬件平台的二进制代码。再由具体操作系统建立进程,把这个二进制文件装进其进程的内存空间中,才能运行。

1.程序编译过程

我们使用命令

gcc HelloWorld.c -o HelloWorld

可以发现编译生成了一个HelloWord的文件,接着使用
./HelloWord

可以发现打印出了HelloWorld!

image-20210704170026728

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b1OU7VSV-1634117695803)(操作系统.assets/image-20210831194048546.png)]

GCC 只是完成编译工作的驱动程序,它会根据编译流程分别调用预处理程序、编译程序、汇编程序、链接程序来完成具体工作。

具体流程图:

image-20210704170254899

其实,我们也可以手动控制以上这个编译流程,从而留下中间文件方便研究:

  • gcc HelloWorld.c -E -o HelloWorld.i 预处理:加入头文件,替换宏。
  • gcc HelloWorld.c -S -c HelloWorld.s 编译:包含预处理,将 C 程序转换成汇编程序。
  • gcc HelloWorld.c -c HelloWorld.o 汇编:包含预处理和编译,将汇编程序转换成可链接的二进制程序。
  • gcc HelloWorld.c -o HelloWorld 链接:包含以上所有操作,将可链接的二进制程序和其它别的库链接在一起,形成可执行的程序文件。

2程序装载执行

图灵机是一个抽象的模型,它是这样的:有一条无限长的纸带,纸带上有无限个小格子,小格子中写有相关的信息,纸带上有一个读头,读头能根据纸带小格子里的信息做相关的操作并能来回移动

img

图灵机执行一下“1+1=2”的计算,我们定义读头读到“+”之后,就依次移动读头两次并读取格子中的数据,最后读头计算把结果写入第二个数据的下一个格子里,整个过程如下图

img

冯诺依曼提出了电子计算机使用二进制数制系统和储存程序,并按照程序顺序执行,他的电子计算机理论叫冯诺依曼体系结构

根据冯诺依曼体系结构构成的计算机,必须具有如下功能:

  • 把程序和数据装入到计算机中;
  • 必须具有长期记住程序、数据的中间结果及最终运算结果;
  • 完成各种算术、逻辑运算和数据传送等数据加工处理;
  • 根据需要控制程序走向,并能根据指令控制机器的各部件协调操作;
  • 能够按照要求将处理的数据结果显示给用户。

为了完成上述的功能,计算机必须具备五大基本组成部件:

  • 装载数据和程序的输入设备;
  • 记住程序和数据的存储器;
  • 完成数据加工处理的运算器;
  • 控制程序执行的控制器;
  • 显示处理结果的输出设备。

根据冯诺依曼的理论,我们只要把图灵机的几个部件换成电子设备,就可以变成一个最小核心的电子计算机

img

这次我们发现读头不再来回移动了,而是靠地址总线寻找对应的“纸带格子”。读取写入数据由数据总线完成,而动作的控制就是控制总线的职责了。

3.更形象地将 HelloWorld 程序装入原型计算机

我们尝试将 HelloWorld 程序装入这个原型计算机,在装入之前,我们先要搞清楚 HelloWorld 程序中有什么

我们通过

objdump -d HelloWorld

可以得到HelloWorld.dump,其中有很多库代码(只需关注 main 函数相关的代码),如下图:

image-20210831194412420

分成四列:第一列为地址;第二列为十六进制,表示真正装入机器中的代码数据;第三列是对应的汇编代码;第四列是相关代码的注释。这是 x86_64 体系的代码,由此可以看出 x86 CPU 是变长指令集。

image-20210705122039689

二.几行汇编几行C:实现一个最简单的内核

在写 Hello OS 之前,我们先要搞清楚 Hello OS 的引导流程,如下图所示

image-20210705122449752

PC 机 BIOS 固件是固化在 PC 机主板上的 ROM 芯片中的,掉电也能保存,PC 机上电后的第一条指令就是 BIOS 固件中的,它负责检测和初始化 CPU、内存及主板平台,然后加载引导设备(大概率是硬盘)中的第一个扇区数据,到 0x7c00 地址开始的内存空间,再接着跳转到 0x7c00 处执行指令,在我们这里的情况下就是 GRUB 引导程序。

就是一上电,就执行BIOS的指令,CS:IP指向BIOS,BIOS指令会检查和各种硬件,将磁盘0磁道0扇区读入到内存的0x7C00中,然后设置cs=0x07c0,ip=0x000,那么cs:ip就是指向0x7c00的位置,就是刚才从磁盘读入的内容

1.Hello OS 引导汇编代码

我们先来写一段汇编代码。但是为什么不能直接用 C?

C 作为通用的高级语言,不能直接操作特定的硬件,而且 C 语言的函数调用、函数传参,都需要用栈。

栈简单来说就是一块内存空间,其中数据满足后进先出的特性,它由 CPU 特定的栈寄存器指向,所以我们要先用汇编代码处理好这些 C 语言的工作环境。

entry.asm

MBT_HDR_FLAGS EQU 0x00010003
MBT_HDR_MAGIC EQU 0x1BADB002 ;多引导协议头魔数
MBT_HDR2_MAGIC EQU 0xe85250d6 ;第二版多引导协议头魔数
global _start ;导出_start符号
extern main ;导入外部的main函数符号
[section .start.text] ;定义.start.text代码节
[bits 32] ;汇编成32位代码
_start:
jmp _entry
ALIGN 8
mbt_hdr:
dd MBT_HDR_MAGIC
dd MBT_HDR_FLAGS
dd -(MBT_HDR_MAGIC+MBT_HDR_FLAGS)
dd mbt_hdr
dd _start
dd 0
dd 0
dd _entry
;以上是GRUB所需要的头
ALIGN 8
mbt2_hdr:
DD MBT_HDR2_MAGIC
DD 0
DD mbt2_hdr_end - mbt2_hdr
DD -(MBT_HDR2_MAGIC + 0 + (mbt2_hdr_end - mbt2_hdr))
DW 2, 0
DD 24
DD mbt2_hdr
DD _start
DD 0
DD 0
DW 3, 0
DD 12
DD _entry
DD 0
DW 0, 0
DD 8
mbt2_hdr_end:
;以上是GRUB2所需要的头
;包含两个头是为了同时兼容GRUB、GRUB2
ALIGN 8
_entry:
;关中断
cli
;关不可屏蔽中断
in al, 0x70
or al, 0x80
out 0x70,al
;重新加载GDT
lgdt [GDT_PTR]
jmp dword 0x8 :_32bits_mode
_32bits_mode:
;下面初始化C语言可能会用到的寄存器
mov ax, 0x10
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
xor eax,eax
xor ebx,ebx
xor ecx,ecx
xor edx,edx
xor edi,edi
xor esi,esi
xor ebp,ebp
xor esp,esp
;初始化栈,C语言需要栈才能工作
mov esp,0x9000
;调用C语言函数main
call main
;让CPU停止执行指令
halt_step:
halt
jmp halt_step
GDT_START:
knull_dsc: dq 0
kcode_dsc: dq 0x00cf9e000000ffff
kdata_dsc: dq 0x00cf92000000ffff
k16cd_dsc: dq 0x00009e000000ffff
k16da_dsc: dq 0x000092000000ffff
GDT_END:
GDT_PTR:
GDTLEN dw GDT_END-GDT_START-1
GDTBASE dd GDT_START

image-20210705122754086

2.Hello OS 的主函数

上面的汇编代码调用了 main 函数,而在其代码中并没有看到其函数体,而是从外部引入了一个符号。

那是因为这个函数是用 C 语言写的在(/lesson01/HelloOS/main.c)中,最终它们分别由 nasm 和 GCC 编译成可链接模块,由 LD 链接器链接在一起,形成可执行的程序文件:

main.c

#include "vgastr.h"
void main()
{
   
  printf("Hello OS!");
  return;
} 

以上这段代码,你应该很熟悉了吧?不过这不是应用程序的 main 函数,而是 Hello OS 的 main 函数。

其中的 printf 也不是应用程序库中的那个 printf 了,而是需要我们自己实现了。

3.控制计算机屏幕

下面,我们来看看显卡的字符模式的工作细节。

它把屏幕分成 24 行,每行 80 个字符,把这(24*80)个位置映射到以 0xb8000 地址开始的内存中,每两个字节对应一个字符,其中一个字节是字符的 ASCII 码,另一个字节为字符的颜色值。如下图所示

image-20210705125621438

明白了显卡的字符模式的工作细节,下面我们开始写代码。

这里先提个醒:C 语言字符串是以 0 结尾的,其字符编码通常是 utf8,而 utf8 编码对 ASCII 字符是兼容的,即英文字符的 ASCII 编码和 utf8 编码是相等的(关于utf8编码你可以自行了解)。

vgastr.c

void _strwrite(char* string)
{
   
  char
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值