浅谈操作系统-启动过程

一、前言

时光匆碌,不知不觉都大三了,在众多的专业课的学习中也算是找到了一些乐趣,纸上得来终觉浅,所以决定完整的回顾一下整个操作系统的知识,为了理论与实践相结合,以学校实验室的EOS操作系统为例进行实践操作,作为一个刚刚开始学习操作系统的新手,如果有说的不对的地方还请多多指教。

EOS 是一个可以在 Intel X86 平台上运行的、面向教学的开源操作系统。为了让 EOS 适合于教学,EOS 被设计的十分小巧,并且尽量保持架构简单。但是,EOS 仍然涵盖了系统引导、进程管理、内存管理、IO 管理、文件系统等重要的操作系统概念。

二、系统启动

我们都知道程序的执行是必须要进去内存之后进行执行,操作系统也可以看做一个程序的运行,如果从开机加载到内存中执行的过程就是系统的启动。我们先宏观上看一下整个从CPU加电到操作系统启动的过程:

  1. BIOS 程序首先将存储设备的引导记录(Boot Record)载入内存,并执行引导记录中的引导程序(Boot);
  2. 引导程序会将存储设备中的操作系统内核载入内存,并进入内核的入口点开始执行
  3. 后操作系统内核完成系统的初始化,并允许用户与操作系统进行交互
浅谈操作系统-启动过程插图1

1、BIOS程序执行过程

BIOS(Basic Input/Output System)是基本输入输出系统的简称。BIOS 能为电脑提供最低级、最直 接的硬件控制与支持,是联系最底层的硬件系统和软件系统的桥梁。为了在关机后使 BIOS 不会丢失,早 期的 BIOS 存储在 ROM 中,并且其大小不会超过 64KB;而目前的 BIOS 大多有 1MB 到 2MB,所以会被存储在 闪存(Flash Memory)中。

BIOS主要的作用:

  • CPU 加电后会首先执行 BIOS 程序,其中 POST(Power-On Self-Test)加电自检程序是执行的第 一个例行程序,主要是对 CPU、内存等硬件设备进行检测和初始化。
  • BIOS 中断调用即 BIOS 中断服务程序,是计算机系统软、硬件之间的一个可编程接口。开机时,BIOS 会通知 CPU 各种硬件设备的中断号,并提供中断服务程序。软件可以通过调用 BIOS 中断对软盘驱动器、键盘及显示器等外围设备进行管理。
  • BIOS 会根据在 CMOS 中保存的配置信息来判断使用哪种设备启动操作系统,并将 CPU 移交给操作系统使用。

执行过程:

1.在CPU加电之后,会把CPU所有寄存器的值设为默认值,除了CS寄存器的值改为0xFFFF,其他寄存器的值都为0,这样,根据CS 和 IP的值就可以找到指令的物理地址0xFFFF:0x0000,也就是0xFFFF0。

2.这时CPU就开始执行在这个位置开始执行,这里存放的一条无条件跳转指令JMP,跳转到BIOS的真正启动代码处。

3.BIOS首先先进行POST(Power-On Self Test,加电后自检)POST的主要检测系统中一些关键设备是否存在和能否正常工作,例如内存和显卡等设备;如果硬件出现问题,主板会发出不同含义的蜂鸣,启动中止。如果没有问题,屏幕就会显示出CPU、内存、硬盘等信息。

4.BIOS 程序在执 行一些必要的开机自检和初始化后,会将自己复制到从 0xA0000 开始的物理内存中并继续执行

5.然后,BIOS 开始搜寻可引导的存储设备(即根据用户指定的引导顺序从软盘、硬盘或是可移动设备)。如果找到,则将存储设备中的引导扇区读入物理内存 0x7C00 处,并跳转到 0x7C00 继续执行,从而将 CPU 交给引导扇区中的 Boot 程序。

以 EOS 操作系统为例,开机时,如果在软盘驱动器中插有一张 EOS 操作系统软盘,则软盘的引导扇区 (大小为 512 字节)就会被 BIOS 程序加载到物理内存的 0x7C00 处。此时的物理内存如图 3-2 所示。其中, 常规内存(640K)与上位内存(384K)组成了在实模式下 CPU 能够访问的 1M 地址空间。并且此时只有两 个区域的空白物理内存可供正在运行的 Boot 程序使用,即用户可用(1)和用户可用(2)。

浅谈操作系统-启动过程插图3

2、Boot 程序的执行过程

总结来说,由于Boot程序大小的限制(512KB),所以Boot程序的功能就是为了加载 Loader程序。

前面的CPU上电及BIOS的工作都不是操作系统能控制的,而从引导扇区开始,就完完全全可由操作系统来控制了,因此,编写引导扇区也是编写操作系统 必要的工作之一。从BIOS跳入引导扇区后,计算机系统引导工作就算完成,怎样把操作系统内核读进内存,然后再安排一条跳转指令跳到内核处执行就是操作系 统开发人员的工作了。

在 Boot 程序执行的过程中,CPU 始终处于实模式状态。Boot 程序利用 BIOS 提供的 int 0x13 中断服 务程序读取软盘 FAT12 文件系统的根目录,在根目录中搜寻 loader.bin 文件。如果 Boot 程序找到了 loader.bin 文件,会继续利用 int 0x13 功能将整个 loader.bin 文件读入从地址 0x1000 起始的物理内存, 最后跳转到 0x1000 处开始执行 Loader 程序,Boot 程序的使命到此结束。

EOS中Boot程序源码:

;***
;
; Copyright (c) 2008 北京英真时代科技有限公司。保留所有权利。
;
; 只有您接受 EOS 核心源代码协议(参见 License.txt)中的条款才能使用这些代码。
; 如果您不接受,不能使用这些代码。
;
; 文件名: boot.asm
;
; 描述: 引导扇区。
;
; 
;
;*******************************************************************************/

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;                               boot.asm
;
;     PC 机加电后,CPU 进入实模式,分段管理内存,最多访问 1M 地址空间(没
; 有打开 A20 的情况下)。CPU 首先执行 BIOS 程序,在 BIOS 完成设备检测等工
; 作后,如果 BIOS 被设置为从软盘启动,则 BIOS 会将软盘的引导扇区(512 字节)
; 加载到物理地址 0x7C00 - 0x7DFF 处,然后将 CPU 的 CS 寄存器设置为 0x0000,
; 将 IP 寄存器设置为 0x7C00,接下来 CPU 就开始执行引导扇区中的程序。
;     由于段界限为 64K,所以在不修改段寄存器的情况下只能访问 0x0000 到 0xFFFF
; 的地址空间,软盘引导扇区就被加载到了此范围内,所以在软盘引导扇区程序中一般
; 不需要修改段寄存器。
;     此时的物理内存应该是下面的样子:
;
;                 +-------------------------------------+----------------------
;          0x0000 |                                     |
;                 |   BIOS 中断向量表 (1K)              |
;                 |   BIOS Interrupt Vector Table       |
;                 |                                     |
;                 +-------------------------------------+
;          0x0400 |   BIOS 数据区 (512 Bytes)           |
;                 |   BIOS Data Area                    |
;                 +-------------------------------------+
;          0x0600 |                                     |
;                 |                                     |
;                 |             用户可用(1)             |   常规内存 (640K)
;                 |                                     |  Conventional Memory
;                 |                                     |
;                 +-------------------------------------+
;          0x7C00 |   软盘引导扇区 (512 Bytes)          |
;                 |   Floppy Boot Sector                |
;                 +-------------------------------------+
;          0x7E00 |                                     |
;                 |                                     |
;                 |             用户可用(2)             |
;                 |                                     |
;                 |                                     |
;                 +-------------------------------------+----------------------
;         0xA0000 |                                     |
;                 |                                     |
;                 |   系统占用 (384K)                   |   上位内存 (384K)
;                 |                                     |   Upper Memory
;                 |                                     |
;                 +-------------------------------------+----------------------
;        0x100000 |                                     |
;                 |                                     |   扩展内存(只有进入保护模式才能访问)
;                 |               不可用                |  Extended Memory
;                 Z                                     Z
;                 |                                     |
;    物理内存结束 |                                     |
;                 +-------------------------------------+----------------------
;
;     EOS 的软盘引导扇区程序选择将 Loader.bin 从第一个用户可用区域的 0x1000 处开始
; 加载,即从 0x1000 到 0x7BFF,所以 Loader 最大只能为 0x7C00 - 0x1000 = 0x6C00
; 个字节。如果在保护模式中按照 4K 大小进行分页,则 Loader 就在一个页面的开始处。
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    org 0x7C00
    jmp short Start
    nop                 ; 这个 nop 不可少

; ----------------------------------------------------------------------
; FAT12 引导扇区头
Oem                     db Engintim   ; OEM String,必须 8 个字节
BytesPerSector          dw 512          ; 每扇区字节数                    ----+
SectorsPerCluster       db 1            ; 每簇多少扇区                        |
ReservedSectors         dw 1            ; Boot 记录占用多少扇区             |
Fats                    db 2            ; FAT 表数                            |
RootEntries             dw 224          ; 根目录文件数最大值             |
Sectors                 dw 2880         ; 扇区总数                          \ BPB
Media                   db 0xF0         ; 介质描述                          // BIOS Parameter Block
SectorsPerFat           dw 9            ; 每 FAT 扇区数                     |
SectorsPerTrack         dw 18           ; 每磁道扇区数                        |
Heads                   dw 2            ; 磁头数                           |
HiddenSectors           dd 0            ; 隐藏扇区数                     |
LargeSectors            dd 0            ; 扇区总数,Sectors 为 0 时使用  ----+
DriveNumber             db 0            ; 驱动器号
Reserved                db 0            ; 保留未用
Signature               db 0x29         ; 引导标记 (0x29)
Id                      dd 0            ; 卷序列号
VolumeLabel             db EOS        ; 卷标,必须 11 个字节
SystemId                db FAT12      ; 文件系统类型,必须 8 个字节
;------------------------------------------------------------------------

; FAT12 文件系统相关的一些变量
FirstSectorOfRootDir    dw 0            ; 根目录的起始扇区号
RootDirectorySectors    dw 0            ; 根目录占用的扇区数量
FirstSectorOfFileArea   dw 0            ; 数据区的起始扇区号
BufferOfFat             dw 0            ; FAT 表缓冲区地址
BufferOfRootDir         dw 0            ; 根目录缓冲区地址

LOADER_ORG              equ 0x1000              ; Loader.bin 的起始地址
MAX_FILE_SIZE           equ 0x6C00              ; Loader.bin 只占用 0x1000 到 0x7C00 的空间
wFilePos                dw  LOADER_ORG          ; 用于加载 Loader.bin 的游标
LoaderFileName          db  "LOADER  BIN"       ; Loader.bin 的文件名
strError:               db  "File Loader.bin not found!"

Start:
    ; 初始化 CPU 的段寄存器为 CS 的值(0),堆栈从 64K 向下增长
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    xor sp, sp
    mov bp, sp

    ; 初始化屏幕
    mov ax, 0x0600      ; AH = 0x06,  AL = 0x00
    mov bx, 0x0700      ; 黑底白字(BH = 0x07)
    xor cx, cx          ; 左上角: (列号  0, 行号  0)
    mov dx, 0x184F      ; 右下角: (列号 79, 行号 24)
    int 0x10

    ; 软驱复位
    xor ah, ah
    xor dl, dl
    int 0x13

    ;
    ; 计算根目录的起始扇区号
    ; FirstSectorOfRootDir = ReservedSectors + SectorsPerFat * Fats
    ;
    mov ax, word [SectorsPerFat]
    movzx bx, byte [Fats]
    mul bx
    add ax, word [ReservedSectors]
    mov word [FirstSectorOfRootDir], ax

    ;
    ; 计算根目录占用的扇区数量
    ; RootDirectorySectors = RootEntries * 32 / BytesPerSector
    ;
    mov ax, word [RootEntries]
    shl ax, 5
    mov bx, word [BytesPerSector]
    div bx
    mov word [RootDirectorySectors], ax

    ;
    ; 计算数据区域的起始扇区号
    ; FirstSectorOfFileArea = FirstSectorOfRootDir + RootDirectorySectors
    ;
    add ax, word [FirstSectorOfRootDir]
    mov word [FirstSectorOfFileArea], ax

    ;
    ; 计算 FAT 缓冲区地址(紧接在引导扇区后)
    ; BufferOfFat = 0x7C00 + BytesPerSector * ReservedSectors
    ;
    mov ax, word [BytesPerSector]
    mul word [ReservedSectors]
    add ax, 0x7C00
    mov word [BufferOfFat], ax

    ;
    ; 计算根目录缓冲区地址(紧接在 FAT 缓冲区后)
    ; BufferOfRootDir = BufferOfFat + BytesPerSector * SectorsPerFat
    ;
    mov ax, word [BytesPerSector]
    mul word [SectorsPerFat]
    add ax, word [BufferOfFat]
    mov word [BufferOfRootDir], ax

    ; 将 FAT1 读入 FAT 缓冲区
    mov ax, word [ReservedSectors]      ; 
    mov cx, word [SectorsPerFat]        ; 一个 FAT 表的扇区数量
    mov bx, word [BufferOfFat]          ; es:bx 指向 FAT 表缓冲区
    call ReadSector

    ; 将根目录读入缓冲区
    mov ax, word[FirstSectorOfRootDir]
    mov cx, word[RootDirectorySectors]
    mov bx, word[BufferOfRootDir]
    call ReadSector

    ; 在根目录中查找 Loader.bin 文件
FindFile:
    mov bx, word [BufferOfRootDir]      ; bx 指向第一个根目录项
    mov dx, word [RootEntries]          ; 根目录项总数
    cld

CompareNextDirEntry:
    mov si, LoaderFileName              ; si -> "LOADER  BIN"
    mov di, bx                          ; di -> 目录项中文件名字符串
    mov cx, 11                          ; 文件名字符串的长度
    repe cmpsb                          ; 字符串比较
    cmp cx, 0
    je  CheckFileSize                   ; 如果比较了 11 个字符都相等, 表示找到文件

    ; 文件名不一致,继续比较下一个目录项
    add bx, 0x20                        ; bx 指向下一个目录项
    dec dx                              ; 减小剩余目录项
    jnz CompareNextDirEntry

    ; 查找完所有目录项仍没有找到文件,提示出错
    jmp Error

    ; 找到文件后,检查文件的大小
CheckFileSize:
    mov eax, dword [bx + 0x1C]          ; 得到文件的大小
    test eax, eax
    jz Error
    cmp eax, MAX_FILE_SIZE
    ja Error

    ; 开始加载文件
    mov ax, word [bx + 0x1A]            ; 初始化 ax 为文件的第一个簇号
ReadNextCluster:
    push ax                             ; 保存要读取的簇号

    ;
    ; 计算 ax 号簇对应的扇区号,扇区号 = 数据区起始扇区号 + (簇号 - 2) * 每簇扇区数
    ;
    sub ax, 2
    movzx cx, byte [SectorsPerCluster]
    mul cx
    add ax, word [FirstSectorOfFileArea]

    mov bx, word [wFilePos];            ; 文件缓冲区地址

    call ReadSector                     ; 读一个簇

    ;
    ; 文件位置向后移动一个簇的大小
    ; wFilePos = wFilePos + BytesPerSector * SectorsPerCluster
    ;
    mov ax, word [BytesPerSector]
    movzx bx, byte [SectorsPerCluster]
    mul bx
    add ax, word [wFilePos];
    mov word [wFilePos], ax     

    ; 查找 FAT 表,获得下一个要读取的簇
    pop ax                              ; 刚读取的簇号
    mov bx, 3
    mul bx
    mov bx, 2
    div bx
    mov bx, word [BufferOfFat]
    add bx, ax
    mov ax, word [bx]
    test dx, dx
    jz EvenClusterNo
    shr ax, 4
    jmp CheckEOC
EvenClusterNo:
    and ax, 0x0FFF

    ; 根据簇号判断文件是否结束,如没结束则继续读取
CheckEOC:
    cmp ax, 0x0FF7
    jb  ReadNextCluster

    ; 文件读取完毕,关闭软驱马达
    mov dx, 0x03F2
    xor al, al
    out dx, al

    ; Loader.bin 加载完毕,跳转到 Loader.bin 执行
    jmp 0:LOADER_ORG

    ; 出错处理:在屏幕左上角显示错误信息字符串,并且死循环
Error:  
    mov bp, strError
    mov ax, 0x1301              ; AH = 0x13,  AL = 0x01
    mov bx, 0x0007              ; 页号为 0 (BH = 0x00),黑底白字 (BL = 0x07)
    mov cx, 26                  ; 字符串长度
    xor dx, dx
    int 0x10
    jmp $

;----------------------------------------------------------------------------
; 函数名: ReadSector
; 作  用: 从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中
;----------------------------------------------------------------------------
ReadSector:
    push bp
    mov bp, sp
    push cx                     ; 保存 cl
    push bx                     ; 保存 bx

    ;
    ; 计算 柱面号、起始扇区 和 磁头号
    ; 设扇区号为 x
    ;                           ┌ 柱面号 = y >> 1
    ;       x           ┌ 商 y ┤
    ; -------------- => ┤      └ 磁头号 = y & 1
    ;  每磁道扇区数     │
    ;                   └ 余 z => 起始扇区号 = z + 1
    ;
    mov bl, [SectorsPerTrack]   ; bl: 除数
    div bl                      ; y 在 al 中, z 在 ah 中
    inc ah                      ; z ++
    mov cl, ah                  ; cl <- 起始扇区号
    mov dh, al                  ; dh <- y
    shr al, 1                   ; y >> 1 (其实是 y / Heads, 这里 Heads = 2)
    mov ch, al                  ; ch <- 柱面号
    and dh, 1                   ; dh & 1 = 磁头号
    mov dl, [DriveNumber]       ; 驱动器号 (0 表示 A 盘)
    pop bx                      ; 恢复 bx

.GoOnReading:
    mov ah, 2                   ; 读
    mov al, byte [bp-2]         ; 读 al 个扇区
    int 0x13
    jc  .GoOnReading            ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止

    ; 恢复堆栈并返回
    pop cx
    pop bp
    ret

    ;
    ; 引导扇区代码结束,填充剩下的空间,使生成的二进制代码恰好为 512 字节
    ;
    times   510-($-$$)   db  0
    dw  0xaa55                  ; 引导扇区激活标志

3、Loader 程序的执行过程

Loader 程序的任务和 Boot 程序很相似,同样是将其它的程序加载到物理内存中,这次加载的是操作系统(EOS)内核。除此之外,Loader 程序还负责检测内存大小,为内核准备保护模式执行环境等工作。简单来说,Loader程序就是从软盘的根目录将内核文件kernel.dll载入物理内存0x10000,然后通过开启分页机制,映射到虚拟地址0x80000000处,然后Loader程序跳转到kernel.dll的入口点继续执行,到此,控制权交给了内核。

Loader程序的汇编实现相当复杂,就不在此贴出来了

4、内核初始化过程

总的来说,内核初始化的主要是初始化处理器和中断、各个管理模块、最后创建主进程。之后启动控制台程序,这样就可以交互,进行应用程序的执行了

EOS中内核的入口程序:

/***

Copyright (c) 2008 北京英真时代科技有限公司。保留所有权利。

只有您接受 EOS 核心源代码协议(参见 License.txt)中的条款才能使用这些代码。
如果您不接受,不能使用这些代码。

文件名: start.c

描述: EOS 内核的入口函数。



*******************************************************************************/

include "ki.h"
include "mm.h"
include "ob.h"
include "ps.h"
include "io.h"
include "kdb.h"

VOID
KiSystemStartup(
    PVOID LoaderBlock
    )
/*++

功能描述:
    系统的入口点,Kernel.dll被Loader加载到内存后从这里开始执行。

参数:
    LoaderBlock - Loader传递的加载参数块结构体指针,内存管理器要使用。

返回值:
    无(这个函数永远不会返回)。

注意:
    KiSystemStartup在Loader构造的ISR栈中执行,不存在当前线程,所以不能调用任何可
    能导致阻塞的函数,只能对各个模块进行简单的初始化。

--*/
{
    //
    // 初始化处理器和中断。
    //
    KiInitializeProcessor();
    KiInitializeInterrupt();

    //
    // 初始化内核调试桩。
    // 注意:在调试桩初始化完成之前,设置断点是不会命中的。
    //
ifdef _DEBUG
    KdbInitializeSystem();
endif

    //
    // 初始化可编程中断控制器和可编程定时计数器。
    //
    KiInitializePic();
    KiInitializePit();

    //
    // 对各个管理模块执行第一步初始化,顺序不能乱。
    //
    MmInitializeSystem1(LoaderBlock);
    ObInitializeSystem1();
    PsInitializeSystem1();
    IoInitializeSystem1();

    //
    // 创建系统启动进程。
    //
    PsCreateSystemProcess(KiSystemProcessRoutine);

    //
    // 执行到这里时,所有函数仍然在使用由 Loader 初始化的堆栈,所有系统线程
    // 都已处于就绪状态。执行线程调度后,系统线程开始使用各自的线程堆栈运行。
    //
    KeThreadSchedule();

    //
    // 本函数永远不会返回。
    //
    ASSERT(FALSE);
}

三、参考资料

1.EOS 操作系统实验教程 海西慧学 编著2.计算机的启动过程(详细)3.从开机到进入操作系统的引导过程详解

原创文章 浅谈操作系统-启动过程,版权所有
如若转载,请注明出处:https://www.itxiaozhan.cn/20222268.html

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注