ucore—1至4讲:bootloader

第一讲:操作系统概述

什么是操作系统

  • ucore操作系统
    在这里插入图片描述
  • 操作系统内核特征
    并发、共享、虚拟、异步

为什么学习操作系统

略…

操作系统实例

略…

操作系统的演变

单用户系统
批处理系统
多道程序系统
分时系统
个人计算机
分布式系统

操作系统结构

  • 简单结构在这里插入图片描述
  • 分层结构
    在这里插入图片描述
  • 微内核结构
    在这里插入图片描述
  • 外核结构
    在这里插入图片描述
    类似于虚拟机

第二讲(实验):环境准备

2.1实验概述

  • 8个实验
    在这里插入图片描述

  • 实验涉及内容
    在这里插入图片描述

2.2 x86-32硬件介绍

80386的四种运行模式

实模式:16位寻址空间,无保护机制。80386加电后默认进入实模式运行
保护模式:32位寻址空间,有保护机制。支持内存分页机制,提供了对虚拟内存的良好支持;保护模式下80386支持多任务、支持优先级

关于内存地址

物理地址:提交到总线上用于访问内存和外设的最终地址,一个计算机系统只有一个物理地址空间
线性地址:在虚拟内存系统的管理之下每个程序能访问的地址空间(它是逻辑地址与物理地址的中间形式)
逻辑地址:应用程序直接使用的地址空间(即进程的地址空间)
关系
地址的转换过程:逻辑地址=> 线性地址 => 物理地址
在这里插入图片描述

x86-32寄存器

  • 8组寄存器
    通用寄存器:使用最多
    段寄存器:段相关,主要用于寻址
    指令指针寄存器(EIP):寻址相关
    标志寄存器(Eflags):寻址相关
    控制寄存器
    系统地址寄存器
    调试寄存器:开发应用用不到
    测试寄存器:开发应用用不到

  • 通用寄存器
    EAX:累加器
    EBX:基址寄存器
    ECX:计数器
    EDX:数据寄存器
    ESI:源地址指针寄存器
    EDI:目的地址指针寄存器
    EBP:基址指针寄存器(注意与基址寄存器的区别)
    ESP:堆栈指针寄存器

  • 段寄存器
    CS:代码段(实模式和保护模式下含义不同)
    DS:数据段
    ES:附加数据段
    SS:堆栈段
    FS:附加段
    GS:附加段

  • 指令指针寄存器(EIP)
    在这里插入图片描述
    没有分段机制时,EIP寄存器存放的就是下一条指令的地址;有分段机制时,EIP存放的是段内偏移
    实模式下(已分段),由CS和EIP共同决定地址 => 即:CS*16+IP
    而保护模式下(已分段),EIP仍然存放段偏移,但是CS存放的是段选择子(通过选择子去找段基址),而不是直接存放段基址。

  • 标志寄存器(EFLAGS)
    EFLAGS使用不同的bit作为标志,比如:
    在这里插入图片描述
    部分bit不能由应用程序修改,只能由运行在特权态的操作系统修改

2.3 ucore相关数据结构与编程技巧

ucore部分编程技巧

第三讲:启动、中断、异常和系统调用

3.1 BIOS(内有疑惑)

  • 加电以后cpu的第一条指令来自哪里?
    系统加电后的初始化代码来自ROM,它是物理内存最低地址的一小段,断电后存储内容不会丢失,BIOS即存储在ROM中。加电后ROM中的BIOS先读取磁盘的MBR,然后…
    更确切的说,是开机时硬件将ROM中的程序读取到RAM中低地址的一小段,详见关于BIOS的入口地址0xFFFF0
  • 初始化时的约定
    刚加电时,处于实模式,只有20位地址空间。此时指令地址的计算方式=>
    PC=16*CS:IP,即CS寄存器左移4位,再加上指令指针寄存器;其中CS代表段基址,IP代表段内偏移
    为什么是20位地址空间:实模式下寄存器只有16位,但是将CS左移4位得到20位的数据,再加上段内偏移IP,结果仍是20位的地址(个人认为寄存器中值的设置应该保证计算得到的PC不会溢出)
    80386reset后CS和IP的值:复位后CS的值为0xf000,IP的值为0xfff0,如下图所示
    疑惑:为什么图中BIOS的开始地址是640kb,如何通过CS、IP计算得到的,按照上文,不是0xffff0吗???
    在这里插入图片描述
  • 加载过程(更详细的过程参考下一节)
    1. BIOS固件将磁盘中MBR里存储的系统加载程序加载到PC=CS:IP=0000:7c00处(这个加载程序只有512B;MBR是磁盘的最开始处)
    2. 然后控制跳转到0x7c00处执行刚从磁盘加载进来的系统加载程序
    3. 系统加载程序将操作系统的代码和数据从磁盘加载到内存
    在这里插入图片描述
    为什么要先加载程序,再由加载程序来加载操作系统?:由于文件系统有不同的格式,BIOS并不能直接识别所有的文件系统,所以它只负责加载系统加载程序,与文件系统相关的事情再交给系统加载程序来处理

3.2 系统启动流程

启动流程

  • 大体过程
    在这里插入图片描述
    注意:如上图所示,实际上BIOS不是直接就读取了加载程序的 => 由于磁盘可能有多个分区,BIOS并不能确定加载程序在哪个分区。
    所以:1. 先读主引导扇区,借此找到活动分区(即系统分区)
    2. 读取活动分区的引导扇区代码
    3. 最后再利用引导扇区代码读取加载程序到内存

  • 0:CPU初始化
    在这里插入图片描述

  • 1:BIOS初始化(硬件自检POST)
    1 检测系统中内存和显卡等关键部件的存在和工作状态
    2 查找并执行显卡等接口卡BIOS,进行设备初始化
    3 执行系统BIOS,进行系统检测 => 检测并配置即插即用设备(比如从U盘启动),从而了解整个计算机系统连接了哪些设备
    4 更新CMOS中的扩展系统配置数据ESCD(存储系统连接的设备,每次加电都会进行)
    5 将控制权转到从外部读入的程序

  • 主引导扇区(MBR)格式
    在这里插入图片描述

  • 分区引导扇区格式

    在这里插入图片描述

  • 加载程序(bootloader)
    BIOS从活动分区加载bootloader后,控制转到bootloader,然后由bootloader在该分区对应的文件系统中读取启动配置信息,最后根据配置加载操作系统内核!!!
    在这里插入图片描述

系统启动规范

BIOS:固化到计算机主板上的程序,包括系统设置,自检程序和系统启动程序,有以下扩展:
BIOS-MBR:一个MBR可以描述四个分区,BIOS读取MBR后找到存放系统的活动分区…
BIOS-GPT:用于解决多余四个分区的问题…
PXE:从网络启动的标准…
UEFI:接口标准,旨在在所有平台上提供一直的操作系统启动服务…

3.3 中断、异常和系统调用

  • 内核的进入与退出
    注:这里可参考csapp第8章=>广义上的异常可分为:中断、陷阱、故障、终止添加链接描述
    本节讲的异常强调的是陷阱和故障; 而系统调用本质上也是通过陷阱实现的,只是它有自己单独的系统调用表,而不是直接使用中断(异常)向量表(系统调用在中断向量表中占用一个编号,发生系统调用时会从中断向量表跳转到系统调用表)
    在这里插入图片描述
  • 三者的区别
    在这里插入图片描述

3.4 系统调用

  • 系统调用的实现
    在这里插入图片描述
  • 系统调用与函数调用的不同
    指令不同:系统调用使用INT指令; 函数调用使用CALL指令
    堆栈切换:系统调用会切换到内核栈;函数调用不会切换堆栈
    调用开销:系统调用开销大于函数调用(需要完成切换引导、内核堆栈建立、地址空间映射、安全验证等)
    在这里插入图片描述

3.5 系统调用示例


  • 在这里插入图片描述

3.6 ucore+系统调用代码

添加链接描述

第四讲(实验1):bootloader启动ucore-os

4.1 x86启动顺序(可参考3.1、3.2)

  • x86 加电后寄存器的初始值
    在这里插入图片描述
    1. 刚加电时,处于实模式,寻址方式兼容8086,使用Base+IP(而不是16CS:IP) => 加电后要取的第一个内存地址是Base+IP(FFFF0000H+0000FFF0H=FFFFFFF0H),这个地址就是BIOS的EPROM所在地,更多参考:附录“启动后第一条执行的指令”
    2. 当CS被新值加载后,16CS:IP的地址计算规则才开始使用
    3. 上述的第一个地址处是一个长跳转指令,它会跳到BIOS代码中取执行…(读取长跳转指令时便会更新CS、EIP寄存器,地址计算规则开始正常使用…)

  • 从BIOS到bootloader
    1. BIOS开始执行后,加载存储设备上的第一个删除(主引导扇区,MBR,共512字节,其中64字节用于描述4个活动分区,最后两字节是结束标志字节,为0x55aa)到内存地址0x7c000处
    2. 控制转移到从MBR读进来的代码,即从0x7c00开始执行…
    (注:本节认为MBR中加载进来的代码就是加载os的bootloader,忽略了3.2中描述的活动分区引导扇区)

  • 从bootloader到os
    a.bootloader开始执行,系统从实模式切换到保护模式(通过设置CR0寄存器的某一bit实现) => 寻址空间变为4G,段机制开始工作
    b.bootloader读取系统内核代码
    c.跳转到系统内核的入口点,控制转移到此,系统开始执行…

  • 段机制
    保护模式下,地址计算不再是16*CS:IP! =>此时段寄存器存放的是一个指针,指向段描述符,段描述符描述了这个段的基址和大小(当然不止这两项),如图:
    在这里插入图片描述
    - 全局描述符表(GDT) 与 地址计算
    如上所述,段模式下,段寄存器存放的称为段选择子(selector),它是到段描述符的指针(准确的说存放的是是索引/下标+一些其他信息)。段描述符存放在全局描述符表(GDT),通过寄存器GDTR可以找到GDT。GDTR中的全局描述符表基址+段选择子中的下标部分,就得到段描述符的地址
    在段描述符中找到段基址,再加上EIP中的偏移即可得到线性地址(段机制下逻辑地址就是线性地址),整个过程如下:
    在这里插入图片描述

  • 段寄存器、全局描述符表项的格式
    段寄存器存放的数字中除了包含GDT下标外,还有其它信息,比如特权级
    GDT表项中除了段机制、段大小外,还有其它信息…
    具体格式求助网络…

  • 加载ELF的系统内核程序
    …略

4.2 C函数调用的实现

  • 函数调用栈的变化过程
    在这里插入图片描述
    调用foo后
    在这里插入图片描述
    如上图,注意调用新的函数时,EBP指向新的栈帧,但是EBP所指地址存放的内容是上一个栈帧的地址,依次类推便形成了一个调用链
  • 注意
    1.部分参数、返回值也可通过寄存器来保存,从而比放再内存中的栈更高效
    2.保存/恢复寄存器时不一定是保存所有寄存器的值…

4.3 GCC内联汇编

  • 内联汇编
    直接在C中插入汇编代码
    作用:完成部分C语言无法完成的功能
  • 示例
    语法
    在这里插入图片描述

示例1
在这里插入图片描述
示例2
在这里插入图片描述
在这里插入图片描述

4.4 x86-32 中断处理过程

  • 中断源
    x86将(广义)中断分为两类:中断、异常 (注意与csapp中的区别)
    中断的产生
    外部中断:串口、硬盘、网卡、时钟等
    软件中断:INT n指令 => 用于系统调用
    异常的产生
    程序错误:除零等
    机器检查出的异常S
  • 1.确定中断服务例程(Interpt Service Routine)
    每个中断(异常)与一个中断服务例程(ISR)相关联,关联关系存储在中断描述符表IDT中,类似于GDT,IDT的基址存放在寄存器GDTR中
    中断描述符表的表项:最重要的包括段选择子、段偏移(见下一幅图)
    在这里插入图片描述
    中断的查找过程
    a.根据中断号查找中断描述符表IDT,得到中断服务例程的段索引和段偏移
    b.根据a中的段索引查找全局描述符表,得到中断服务程序所处段的基址
    c.根据b中所得的段基址和a中所得的段偏移,找到中断服务程序的起始地址
    在这里插入图片描述
  • 2.切换到中断服务例程
    对于在内核态运行时产生的中断(图左):直接向当前使用的栈中压入部分寄存器的内容,从而完成现场保存
    对于在用户态运行时产生的中断(图右):将当前部分寄存器的内容压入系统栈(而不是用户栈),从而完成现场保存; 此外,还需要压入用户栈的ESP和SS,方便返回时回到用户栈原位
    如何判断内核态还是用户态:这是由优先级(特权级)决定,段寄存器中的某个低位bit表示特权级
    在这里插入图片描述
  • 3.从中断服务例程返回
    iret指令完成中断的返回; ret、retf完成函数的返回!!!
    对于在内核态运行时产生的中断:iret指令使得栈中所有保存的寄存器内容恢复到寄存器中,从而能够根据CS、EIP继续从被中断处执行
    对于在用户态运行时产生的中断:iret指令执行时将系统栈中所有内容恢复到用户栈…
    在这里插入图片描述

4.5-4.9 练习

参考:lab1

4.10 补充:关于第一条指令及线性地址的计算(重要)

写在前面:最好直接参考:lab1实验文档BIOS启动过程
开机后的第一条指令
你真的了解段寄存器吗?

  • 地址转换过程
    逻辑地址(进程地址空间) => 线性地址 => 物理地址
    逻辑地址到线性地址是通过分段机制; 线性地址到物理地址是通过分页机制;如果没有分页机制,则线性地址就是物理地址
    本节主要讨论分段机制下,线性地址的计算

  • 16位机器实模式下的分段相关寄存器
    CS:16位,存放段基址。不过段基址=16* CS
    IP:16位,存放段偏移
    => 线性地址=段基址+段偏移=16*CS+IP

  • 32位机器保护模式下的分段相关寄存器
    CS:这些段寄存器(不仅CS)除了有16位的可见部分,还有不可见的隐藏部分,称为描述符缓存“descriptor cache”或隐藏寄存器“shadow register”。当一个段选择符(segment selector)装入段寄存器的可见部分,处理器同时也把该段描述符的其它数据装入到段寄存器的隐藏部分,这包括段开始的基地址、段长度、访问控制信息等。这些信息缓存到段寄存器中,避免了处理器在转址(translate address)时花费额外的总线周期从段选择符表中读入数据。处理器指令中可以明示使用哪些段寄存器,这将替换掉默认使用的段寄存器。 ————维基百科
    EIP:同样是存放段偏移
    => 线性地址=段基址+段偏移
    =(通过CS中的可见部分找到段基址,将段基址存入CS的不可见部分)+EIP
    可见32位和16位下,线性地址的计算都是基址+偏移。区别在于32位下基址的计算方式发生了改变

  • 为什么第一条指令是0xFFFFFFF0
    32位机器reset后,
    CS:可见部分被设为F000H,不可见部分被设FFFF0000H => 基址为不可见部分,即FFFF0000H
    EIP:被设置为0000FFFOH => 偏移为0000FFF0H
    由于没有分页机制,物理地址=线性地址=…=FFFFFFF0H

  • 刚开机时不是实模式吗,为什么按照保护模式的方法计算第一条指令地址?
    这是规定,第一条指令按照保护模式的方式计算。之后由于CS寄存器被修改,且处于实模式,才开始使用16*CS+IP的方式计算

  • 刚开机时处于16位实模式,地址空间只有1MB,为什么第一条指令地址远大于1MB?
    通过BIOS映射,参考:开机后的第一条指令

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值