首页 > 编程语言 >操作系统实战45讲- 02 几行汇编几行C:实现一个最简单的内核

操作系统实战45讲- 02 几行汇编几行C:实现一个最简单的内核

时间:2022-08-31 17:24:56浏览次数:77  
标签:02 GRUB DD 45 几行 dd main OS Hello

本节源代码位置https://gitee.com/lmos/cosmos/tree/master/lesson02/HelloOS

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

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

更先进的UEFI BIOS则不同,这里就不深入其中了,你可以通过链接自行了解。

Hello OS 引导汇编代码

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

C语言用到了下层操作系统/部分汇编程序创造的内存的数据结构比如栈,所以C语言运行之前必然要先运行下部分汇编程序,而汇编的下层是CPU的硬件逻辑。

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

/lesson02/HelloOS/entry.asm
   3  MBT_HDR_FLAGS   EQU 0x00010003
     4  MBT_HDR_MAGIC   EQU 0x1BADB002 ;多引导协议头魔数
     5  MBT_HDR2_MAGIC  EQU 0xe85250d6 ;第二版多引导协议头魔数
     6  global _start ;导出_start符号
     7  extern main ;导入外部的main函数符号
     8
     9  [section .start.text] ;定义.start.text代码节
    10  [bits 32] ;汇编成32位代码
    11  _start:
    12          jmp _entry
    13  ALIGN 8
    14  mbt_hdr:
    15          dd MBT_HDR_MAGIC
    16          dd MBT_HDR_FLAGS
    17          dd -(MBT_HDR_MAGIC+MBT_HDR_FLAGS)
    18          dd mbt_hdr
    19          dd _start
    20          dd 0
    21          dd 0
    22          dd _entry
    23
    24  ;以上是GRUB所需要的头
    25  ALIGN 8
    26  mbt2_hdr:
    27          DD      MBT_HDR2_MAGIC
    28          DD      0
    29          DD      mbt2_hdr_end - mbt2_hdr
    30          DD      -(MBT_HDR2_MAGIC + 0 + (mbt2_hdr_end - mbt2_hdr))
    31          DW      2, 0
    32          DD      24
    33          DD      mbt2_hdr
    34          DD      _start
    35          DD      0
    36          DD      0
    37          DW      3, 0
    38          DD      12
    39          DD      _entry
    40          DD      0
    41          DW      0, 0
    42          DD      8
    43  mbt2_hdr_end:
    44  ;以上是GRUB2所需要的头
    45  ;包含两个头是为了同时兼容GRUB、GRUB2
    46
    47  ALIGN 8
    48
    49  _entry:
    50          ;关中断
    51          cli
    52          ;关不可屏蔽中断
    53          in al, 0x70
    54          or al, 0x80
    55          out 0x70,al
    56          ;重新加载GDT
    57          lgdt [GDT_PTR]
    58          jmp dword 0x8 :_32bits_mode
    59
    60  _32bits_mode:
    61          ;下面初始化C语言可能会用到的寄存器
    62          mov ax, 0x10
    63          mov ds, ax
    64          mov ss, ax
    65          mov es, ax
    66          mov fs, ax
    67          mov gs, ax
    68          xor eax,eax
    69          xor ebx,ebx
    70          xor ecx,ecx
    71          xor edx,edx
    72          xor edi,edi
    73          xor esi,esi
    74          xor ebp,ebp
    75          xor esp,esp
    76          ;初始化栈,C语言需要栈才能工作
    77          mov esp,0x9000
    78          ;调用C语言函数main
    79          call main
    80          ;让CPU停止执行指令
    81  halt_step:
    82          halt
    83          jmp halt_step
    84
    85
    86  GDT_START:
    87  knull_dsc: dq 0
    88  kcode_dsc: dq 0x00cf9e000000ffff
    89  kdata_dsc: dq 0x00cf92000000ffff
    90  k16cd_dsc: dq 0x00009e000000ffff
    91  k16da_dsc: dq 0x000092000000ffff
    92  GDT_END:
    93
    94  GDT_PTR:
    95  GDTLEN  dw GDT_END-GDT_START-1
    96  GDTBASE dd GDT_START

以上的entry.asm汇编代码en分为 4 个部分:

  1. 代码 1~45 行,用汇编定义的 GRUB 的多引导协议头,其实就是一定格式的数据,我们的 Hello OS 是用 GRUB 引导的,当然要遵循 GRUB 的多引导协议标准,让 GRUB 能识别我们的 Hello OS。之所以有两个引导头,是为了兼容 GRUB1 和 GRUB2。

  2. 代码 47~58 行,关掉中断,设定 CPU 的工作模式。

  3. 代码 60~80 行,初始化 CPU 的寄存器和 C 语言的运行环境。

  4. 代码 86~96 行,GDT_START 开始的,是 CPU 工作模式所需要的数据

Hello OS 的主函数

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

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

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

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

控制计算机屏幕

计算机屏幕显示往往是显卡的输出,显卡有很多形式:集成在主板的叫集显,做在 CPU 芯片内的叫核显,独立存在通过 PCIE 接口连接的叫独显,性能依次上升,价格也是

我们要在屏幕上显示字符,就要编程操作显卡。

其实无论我们 PC 上是什么显卡,它们都支持一种叫 VESA 的标准,这种标准下有两种工作模式:字符模式和图形模式。显卡们为了兼容这种标准,不得不自己提供一种叫 VGABIOS 的固件程序。

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

C 语言字符串是以 0 结尾的,其字符编码通常是 utf8,而 utf8 编码对 ASCII 字符是兼容的,即英文字符的 ASCII 编码和 utf8 编码是相等的。utf8标准

vgastr.c
void _strwrite(char* string)
{
    char* p_strdst = (char*)(0xb8000);
    while (*string)
    {

        *p_strdst = *string++;
        p_strdst += 2;
    }
    return;
}

void printf(char* fmt, ...)
{
    _strwrite(fmt);
    return;
}

printf 函数直接调用了 _strwrite 函数,而 _strwrite 函数正是将字符串里每个字符依次定入到 0xb8000 地址开始的显存中,而 p_strdst 每次加 2,这也是为了跳过字符的颜色信息的空间

编译和安装 Hello OS

Hello OS 的文件数量会爆炸式增长,一个成熟的商业操作系统更是多达几万个代码模块文件,几千万行的代码量,是这世间最复杂的软件工程之一。所以需要一个牛逼的工具来控制这个巨大的编译过程

make 工具

ake 是一个工具程序,它读取一个叫“makefile”的文件,也是一种文本文件,这个文件中写好了构建软件的规则,它能根据这些规则自动化构建软件。

makefile 文件中规则是这样的:首先有一个或者多个构建目标称为“target”;目标后面紧跟着用于构建该目标所需要的文件,目标下面是构建该目标所需要的命令及参数。

与此同时,它也检查文件的依赖关系,如果需要的话,它会调用一些外部软件来完成任务。

第一次构建目标后,下一次执行 make 时,它会根据该目标所依赖的文件是否更新决定是否编译该目标,如果所依赖的文件没有更新且该目标又存在,那么它便不会构建该目标。这种特性非常有利于编译程序源代码。

一个有关 makefile 的例子

CC = gcc #定义一个宏CC 等于gcc
CFLAGS = -c #定义一个宏 CFLAGS 等于-c
OBJS_FILE = file.o file1.o file2.o file3.o file4.o #定义一个宏
.PHONY : all everything #定义两个伪目标all、everything
all:everything #伪目标all依赖于伪目标everything
everything :$(OBJS_FILE) #伪目标everything依赖于OBJS_FILE,而OBJS_FILE是宏会被
#替换成file.o file1.o file2.o file3.o file4.o
%.o : %.c
   $(CC) $(CFLAGS) -o $@ $<
  1. make 规定“#”后面为注释,make 处理 makefile 时会自动丢弃。

  2. makefile 中可以定义宏,方法是在一个字符串后跟一个“=”或者“:=”符号,引用宏时要用“\((宏名)”,宏最终会在宏出现的地方替换成相应的字符串,例如:\)(CC) 会被替换成 gcc,$( OBJS_FILE) 会被替换成 file.o file1.o file2.o file3.o file4.o。

  3. .PHONY 在 makefile 中表示定义伪目标。所谓伪目标,就是它不代表一个真正的文件名,在执行 make 时可以指定这个目标来执行其所在规则定义的命令。但是伪目标可以依赖于另一个伪目标或者文件,例如:all 依赖于 everything,everything 最终依赖于 file.c file1.c file2.c file3.c file4.c。

  4. 虽然我们会发现,everything 下面并没有相关的执行命令,但是下面有个通用规则:“%.o : %.c”。其中的“%”表示通配符,表示所有以“.o”结尾的文件依赖于所有以“.c”结尾的文件。

例如:file.c、file1.c、file2.c、file3.c、file4.c,通过这个通用规则会自动转换为依赖关系:file.o: file.c、file1.o: file1.c、file2.o: file2.c、file3.o: file3.c、file4.o: file4.c。

然后,针对这些依赖关系,分别会执行:$(CC) $(CFLAGS) -o $@ \(< 命令,当然最终会转换为:gcc –c –o xxxx.o xxxx.c,这里的“xxxx”表示一个具体的文件名。\)@ 目标文件 $< 依赖文件

编译

下面我们用一张图来描述我们 Hello OS 的编译过程

安装 Hello OS

经过上述流程,我们就会得到 Hello OS.bin 文件,但是我们还要让 GRUB 能够找到它,才能在计算机启动时加载它。

GRUB 在启动时会加载一个 grub.cfg 的文本文件,根据其中的内容执行相应的操作,其中一部分内容就是启动项。

GRUB 首先会显示启动项到屏幕,然后让我们选择启动项,最后 GRUB 根据启动项对应的信息,加载 OS 文件到内存。

输入命令:df /boot/,获取boot目录的挂载分区
Filesystem     1K-blocks  Used Available Use% Mounted on
/dev/sda2        1515376 76332   1344020   6% /boot
Hello OS 的启动项,Ubuntu20.04下在/boot/grub/grub.cfg
menuentry 'HelloOS' {
    insmod part_msdos   #GRUB加载分区模块识别分区
    insmod ext2        #GRUB加载ext文件系统模块识别ext文件系统
    set root='hd0,gpt2' #注意boot目录挂载的分区 
    multiboot2 /HelloOS.bin #GRUB以multiboot2协议加载HelloOS.bin
    boot                  #GRUB启动HelloOS.bin
}

添加完成后的实际效果图如下:

强制保存后,重启。

重启时,VMware Workstaion Pro下需要长按shift键进入grub界面

如图:

标签:02,GRUB,DD,45,几行,dd,main,OS,Hello
From: https://www.cnblogs.com/xyjk1002-rejuvenation/p/16632233.html

相关文章

  • go语言函数详解-02
    go语言defer(延迟执行语句)会用延迟执行语句在函数退出时释放资源处理业务或逻辑中涉及成对的操作是一件比较烦琐的事情,比如打开和关闭文件、接收请求和回复请求、加锁和......
  • TPA3045-ASEMI光伏二极管TPA3045
    编辑-ZTPA3045是一款光伏逆变器二极管。TPA3045的浪涌电流Ifsm为300A,漏电流(Ir)为0.1mA,其工作时耐温度范围为-55~200摄氏度。TPA3045采用肖特基势垒芯片,典型热阻(ReJA)为0.8......
  • ASEMI光伏二极管GMK3045,GMK3045参数,GMK3045规格
    编辑-ZASEMI光伏二极管GMK3045参数:型号:GMK3045重复峰值反向电压(VRRM):45V最大有效值电压(VRMS):31.5V平均整流输出电流(IO):30A浪涌(非重复)正向电流(IFSM):600A热阻(典型值)(ReJA):1.......
  • 前端面试题每日3题——2022/08/30
    1以下代码执行后,控制台中的输出内容为?for(leti=0;i<3;i++){setTimeout(()=>{console.log(i);});}for(varj=0;j<3;j++){setTimeout......
  • 2022-8-31 每日一题-栈模拟-剑指offer-二分查找
    946.验证栈序列难度中等303收藏分享切换为英文接收动态反馈给定 pushed 和 popped 两个序列,每个序列中的 值都不重复,只有当它们可能是在最初空栈上进行的推入......
  • C20220801T2 marisa
    考场上写挂这一道题,白给。(数组开小+随机化次数太少)没想到评测机这么给力,直接随机化\(2\times10^5\)个点,只要有一个在所有带状区域之外就没有覆盖,否则可以视为覆盖,这里......
  • C20220805T3 零和
    当构造出长度为22的随机\([1,5]\)的集合后,出现合法方案的概率很大,所以可以先随便构造一种方案,然后再通过背包求出其他取值中可以满足的方案数(即先构造22个极小的整数,去找......
  • C20220805T2 赌徒
    设手中硬币的大小为\(a\)和\(b\),对手硬币的两面是\(a_i\)和\(b_i\),那么单次游戏的收益就是\[\frac{1}{4}x_i(f(a,a_i)+f(a,b_i)+f(b,a_i)+f(b,b_i))\]其中\(f(x......
  • C20220806T1 暴力计算
    给定一张图,按照边权走,求总边权达到\(M\)时用的最短长度。\(n\leq100,M\leq10^{18}\)。首先可以用\(dp[i][j][k]\)表示从\(i\)出发通过\(2^k\)步走到\(j\)......
  • C20220806T3 如何愉快地与方格玩耍
    给定\(n\timesn\)的黑白方格,期初所有颜色均为白色,支持以下操作翻转\([l,r]\)行/列的颜色翻转质数/合数行/列的颜色求\([l1,r1]\)行、\([l2,r2]\)列围成的区......