【现代操作系统】2.ARM64硬件结构与系统结构

原始模型

  • 图灵机

  • 冯诺依曼架构
    冯诺依曼架构

    • 输入输出:和设备之间交互数据
    • CPU:包括处理单元和控制单元
    • 存储器:内存

    经典计算模式
    经典计算模式

    • CPU:解析指令
    • 内存:存储指令和数据

冯诺依曼有什么局限?

  1. CPU和内存的交互引起的内存墙问题(CPU速度越来越快,内存的带宽成为发展瓶颈)
  2. 数据和指令不区分,会出现数据等指令或指令等数据的状态(哈弗结构将指令和数据在缓存中分开)
  3. 串行顺序处理,缺乏数据并行能力

为什么选择ARM

  1. 智能手机的模式指令集
    在这里插入图片描述
  2. ARM正在走向服务器
    华为鲲鹏处理器在这里插入图片描述

ARM发展

  • ARMv4及之前
    • ARM 32位指令集
  • ARMv4T、ARMv5TE
    • 加入Thumb 16位指令集
  • ARMv6
    • 多核、SIMD、TrustZone
    • Thumb-2 32位指令集
  • ARMv7
    • 增强SIMD扩展——NEON
    • 全面支持平台操作系统,如Linux
    • 可预测实时高性能
  • ARMv8
    • 扩大物理寻址
      4GB以外的物理地址
    • 64位虚拟地址
    • 自动时间信号
      低功耗、高性能的自旋锁
    • 硬件加速加密
    • 新的异常模型
      ARMv8的SOC的基本结构
      这里用到了Processor cluster,用cluster是因为现在芯片要考虑到功耗,比如手机里面很多时候并不是要所有核都处于一个最高的频率,所以早期的时候会有大核小核来处理不同的任务。
      大核处理器更复杂,主频更高;小核处理频率低,功能相对简单
      故一些低功耗的操作可以在小核上去做,可以节能,对手机来说续航是非常关键的;一些和用户体验非常相关的,比如游戏,可以在大核上运行
      现在ARMv8上有大中小三种核,这样可以根据需要在不同cluster上执行。

      对于处理器就会相关的时钟(Timers)定制器,这对调度是非常关键的

      下面address decoding有一些地址的转化
      里面的secure就比如TrustZone,是安全分区,把一些安全的敏感的操作放到TrustZone中运行。其中他的内存有安全的分区(Secure RAM),外设也有安全的分区(Secure peripheral),中断也有安全的划分(GIC:通用中断控制器)
      Non-secure是正常运行的操作,比如说Android、ios运行的非安全分区的,里面也有相应的内存(RAM)、外设、系统控制器、网络等,通用对应有中断控制器,对中断进行管理

CPU与指令集架构

ISA:指令集架构,是CPU和软件之间的桥梁
ISA包含指令集、特权级、寄存器、执行模式、安全扩展、性能加速扩展等方面

指令集

指令集是ISA的重要组成部分,通常包含一系列不同功能的指令

这里用到的硬件体系结构:AARCH64体系结构-ARM64位架构
AARCH64:ARM ARCH 64
指令集属于精简指令集计算机(RISC)
AArch64中每条指令的长度固定位4字节,指令类型包括:

  • 数据搬移指令(如mov)
  • 寄存器计算指令(add、sub)
  • 内存读写指令(ldr、str)
  • 跳转指令(无条件跳转b)
  • 过程调用指令(调用指令bl、返回指令ret)
  • 特权指令(读取系统寄存器指令msr、写入系统寄存器指令mrs)

ARMv8支持的执行模式

  • Aarch64:支持A64指令集(实际支持32和64混跑)
  • Aarch32:支持A32、T32、T16指令集
    会出现情况:OS运行在64位的,但是应用运行在32位,甚至 里面还有16位的指令集
    ARM开发板
    右边是华为的麒麟970开发板

Aarch64实现

在这里插入图片描述

  • PC指向当前执行的指令
  • 指令长度相同(RISC,32bit)
  • PC会被跳转指令修改:B,BL,BX,BLX

寄存器ARM

与x86不同的是,ARM是一个RISC的指令集,他的通用寄存器是非常多的

  • 31个64位通用寄存器
    • X0-X30(其中X29用作帧指针FP寄存器,一般用于保存函数调用过程中栈顶的地址;X30用作链接指针LP寄存器,因为CPU在执行函数调用指令bl时会自动把返回地址保存在其中)
  • 1个PC寄存器
  • 4个栈寄存器(切换时保存SP)(SP:stack pointer)
    • SP_EL0, SP_EL1, SP_EL2, SP_EL3
    • 与X86不同的是,X86保存在栈上面,这里用了4个栈寄存器,同时是在不同的模式里面
    • 实际是4个特征集:EL0是用户态的,EL1是操作系统的,EL2是虚拟机的,EL3是vmware等的
    • 这样用4个特征集来保存SP,省去了把SP保存在栈上的操作
    • 这样在做上下文切换的时候就要考虑如何保存和恢复SP
  • 3个异常链接寄存器(保存异常的返回地址)
    • ELR_EL1, ELR_EL2, ELR_EL3(没有EL0,因为这里EL0会跳到EL1中)
  • 3个程序状态寄存器(切换时保存Program STATE)
    • SPSR_EL1, SPSR_EL2, SPSR_EL3(这里也是3个特征集!)

对应到x86-64

  • 16个通用寄存器
  • 1个%rip寄存器–PC
  • 1个%rsp寄存器–跨模式转换时需要压栈
    返回地址压栈
    EFLAGS–程序状态寄存器

X86架构中,切换特权级时rsp是如何保存,以及如何恢复的?
把相关的rsp保存到相关的栈中,返回时像函数调用一样将其恢复出来
这样相对来说就多了两次内存操作

在EL1特权级下有两个页表基地址寄存器(Translation Table Base Register,TTBR),即TTBR0_EL1和TTBR1_EL1,他们负责翻译虚拟地址空间中不同的地址段,负责的地址范围由另一个控制寄存器TCR_EL1(翻译控制寄存器,Translation Control Register)决定
另一种常见配置见下面“内存空间”所示

指令集

  • RISC
    • 固定长度指令格式
    • 更多的通用寄存器
    • Load/store结构(只有load和store访问内存,其他都不行)
    • 简化寻址方式
  • RISC vs CISC
    RISC vs CISC
    ARM闻名是以低功耗著称的

RISC和CISC指令集的优劣

“技术出现时是有明显界限的,在发展后实际上两个是融合的”
CISC是先转化为内部的RISC指令集,再进行微处理结构的设计
所以在微处理结构设计角度来说,两者原理是相似的
RISC也在扩展自己的指令集

特权级

特权级也是ISA的重要组成部分
AArch64中的特权级被称为异常级别(Exception Level,EL),共有4种特权级

ARM(Exception Level)

在这里插入图片描述

  • 非安全分区有4分层级
    • EL0:用户态(应用的特权级),最低的特权级,应用程序通常运行在该特权级
    • EL1:内核态(操作系统的),操作系统通常运行在该特权级
    • EL2:hypervisor(虚拟机监控器,VMM),在虚拟化场景下使用
    • EL3:monitor,和安全特性TrustZone有关,负责普通世界(normal world)和安全世界(secure world)之间的切换

TrustZone是从ARMv6体系结构开始引入的安全特性
该特性从逻辑上将整个系统分为安全世界和普通世界,计算资源可以被划分到这两个世界中

  • 安全世界可以不受限制的访问所有计算资源
  • 普通世界不能访问被划分到安全世界的计算资源
  • 安全分区也有2个EL
    • EL0
    • EL1
    • 最新的一些硬件实现也开始在TrustZone中增加EL2特权级

CPU在提供4种特权级的同时也提供了特权级之间切换的方式

  • 一般从EL0切换到EL1的可能场景有三种:
    • (同步的CPU特权级切换,由CPU中正在执行的指令导致的)应用程序需要调用操作系统提供的系统调用,此时应用程序会通过执行svc(特权调用,supervisor call)指令将CPU特权级从EL0切换到EL1
    • (同步的CPU特权级切换)应用程序执行了一条指令而该指令触发了异常,该异常导致CPU特权级从EL0切换到EL1(如访存指令触发缺页异常)
    • (异步的CPU特权级切换,不是由CPU中运行应用程序中的指令导致的)应用程序在执行过程中,CPU收到一个来自外设的中断,该中断也会导致CPU特权级从EL0切换到EL1
  • 发生特权转换时,CPU保存当前执行状态,以便OS内核在处理异常时使用并在处理结束后能恢复应用程序的执行
    • 触发异常的指令地址(当前程序计数器PC),保存在ELR_EL1(异常链接寄存器,Exception Link Register)
    • 异常原因(异常时由于执行svc指令还是由于访问缺页导致的),保存在ESR_EL1(异常症状寄存器,Exception Syndrome Register)中
    • CPU将栈指针(SP)从SP_EL0(应用程序使用的栈)切换到SP_EL1(OS可以通过设置这个寄存器配置处理过程中使用的栈)
    • 其他信息如将CPU的相关状态保存在SPSR_EL1(已保存的程序状态寄存器,Saved Program Status Register),将引发缺页异常的地址保存在FAR_EL1(错误地址寄存器,Fault Address Register)
      在这里插入图片描述
  • 处理完毕后调用eret指令进行返回
    • ELR_EL1 -> PC恢复PC状态
    • SPSR_EL1 -> PSTATE 恢复处理器状态
    • 降至EL0,硬件自动使用SP_EL0作为栈指针
    • 恢复执行

X84-64

在这里插入图片描述

  • Non-root(跑虚拟机,包括启动操作系统和应用)
    • Ring 0:Guest app
    • Ring 3:Guest os
  • Root(虚拟机监控器)
    • Ring 0:App
    • Ring 3:Hypervisor

系统状态寄存器

X86-64

在这里插入图片描述
用户态和操作系统看到的EFLAG是不一样的

ARM

在这里插入图片描述

  • 抽象进程状态信息(PSTATE)
    • 条件标记(Condition flags)
    • 执行状态(Execution Statement controls)
    • 异常掩码(Exception mask bits)
    • 访问,时钟控制(Access control bits)

系统控制寄存器(control register)

x86-64

在这里插入图片描述

  • CR0:整体控制
  • CR1:在上图没有用到
  • CR2:发生Page-Fault后知道从哪个寄存器中获取page-fault的地址
  • CR3:页表的根目
  • CR4:一系列的状态位

ARM

  • 对系统的顶层控制
    大小端、使用MMU、检查Tag、内存系统在这里插入图片描述
    • 大端:传统网络处理用的比较多,但现在在往小端归一
    • 是否开MMU等可以在状态位上得到体现

内存系统相关寄存器

  • TCR:Translation Control Register (页表翻译控制的寄存器,来控制如何对页表进行管理)
    在这里插入图片描述

  • TTBR:Translation Table Base Register(对应X86-64的CR3)
    在这里插入图片描述

Aarch64的TTBR0支持(0~248-1)的地址映射,TTBR1支持(248 ~264 )的地址映射,这样的硬件设计与x86-64中的CR3相比较,能够如何协助到操作系统的设计?

  • X86的CR3,如果只有一个页表的话,kernel和用户态实际上是在不同的地址空间的,这时如果出现user到kernel的转换的话,就会有一些切换页表的过程
  • 如果对不同的特权级用不同的列表,那么就不用再做页表的切换

地址翻译

在这里插入图片描述

内存空间

在这里插入图片描述
上图是对应内存空间的划分
注意看kernel space 和user space的地址空间
Kernel space和user space的中间有个空洞,为了防止kernel的地址空间和user的地址空间出现非法映射。用户的space在比较低的位置,kernel的space在比较高的位置,这样就把两部分分开来了

这里看到的映射是TTBR1_EL1对应上面的映射,TTBR0_EL0对应下面的映射

ARM输入/输出

  • MMIO与PIO

    • MMIO(Memory-mapped IO)
      • 将设备映射到连续的物理内存中,使用相同的指令去操作
      • 如,Raspi3映射到0x3F200000
      • 行为与内存不完全一样,读写会有副作用
    • PIO(Port IO)
      • IO设备具有独立的地址空间
      • 使用特殊的指令(如x86中的in/out指令)
  • MMIO(memory map IO)

    • 复用ldr和str指令
    • 映射到物理内存的特殊地址段
  • 如下代码对外设进行操作

    unsigned int early_uart_recv(void)		// 对uart进行接收操作(收某个字符)
    {
    	while(1) // 等他的状态位是ready了
    	{
    		if (early_uart_lsr() & 0x01)  // 进这里是不ready
    			break;
    		}
    	return early_get32(AUX_MU_IO_REG) & 0XFF;	// 再将里面的内容(比如是AUX_MU_IO_REG)读出
    }
    
    void early_uart_send(unsigned int c)
    {
    	while(1){ //等可以接受数据的时候
    		if (early_uart_lsr() & 0x20)
    			break;
    	}
    	early_put32(AUX_MU_IO_REG,c);	//再接受数据
    }
    
    • UART (Universal Asynchronous Receiver/Transmitter,通用异步收发器) 是一种用于实现串行通信的硬件接口。UART 允许设备通过串行数据链路发送和接收数据,常用于微控制器与外围设备之间的通信
    • 里面early_get32对应下来的话就是用MMIO实现的,用的是load指令ldr
      BEGIN_FUNC(early_get32)
      	ldr w0, [x0]
      	ret
      END_FUNC(early_get32)
      
    • early_put32也是MMIO,用的str
      	BEGIN_FUNC(early_put32)
      		str w1,[x0]
      		ret
      	END_FUNC(early_put32)
      

操作系统启动过程

以ChCore为例

设备上电后…

  • OS如何启动
    • 不同ARM设备启动OS的方式不同
      • Raspi 3:CPU进行初始化后从SD卡上加载
      • Hikey970:通过BL(Bootloader)链和UEFI加载
    • OS启动时进行硬件初始化工作,并开启页表
    • 进入内核
  • ChCore Bootliader
    • Bootloader和kernel放在同一个ELF文件中
      • Bootloader位于.init段,并通过链接器设置入口
      • Kernel位于.text段
      set_property(
      	TARFER kernel.img
      	APPEND_STRING
      	PROPERTY
      		LINK_FLAGS
      		"-T ${CMAKE_CURRENT_BINARY_DIR}/${link_script} -e _start"
      )
      
      可以知道入口是这个_start
    • 主CPU启动,其他次CPU等待
      BEGIN_FUNC(_start)
      	mrs x8, mpidr_el1
      	mov x9, #0xc1000000
      	bic x8, x8, x9
      	cbz x8, primary
      

层级切换

因为OS是运行在EL1的,所以要从其他特权级跳转到EL1特权级
在这里插入图片描述

  • 其他EL(>=1)到EL1
    Lno_gic_sr:
    	// Set EL1 to 64 bit.设置EL1是64位的
    	mov	x9,HCR_EL2_RW
    	msr	hcr_el2, x9
    	
    	// Set the return address and exception level.设置返回地址和Exception level
    	adr x9, .Ltarget
    	msr elr_el2, x9
    	mov x9, #(SPSR_ELX_DAIF | SPSR_ELX_EL1H)
    	msr spsr_el2, x9
    	
    	isb		//类似于内存屏障的操作
    	eret	//返回
    	
    .Ltarget:
    	ret
    // 要注意在ARM 中写汇编/底层代码时要注意他是一个弱内存序的体系结构
    // 要保证在预return之前要保证前面这些指令都已经commit以后才可以return
    

准备函数栈和异常向量

在这里插入图片描述

  • 准备C函数栈
    • 设置SP寄存器
  • 设置异常向量
    • 为了debug

初始化UART

通过UART协议进行输出

  • 根据UART协议进行内存空间初始化
    • 映射到IO的内存空间
      在这里插入图片描述
  • AUX那些是对应UART里面的一些寄存器,这里是对他进行一些设置
  • 设置好以后进行一下delay,使上面的操作能够生效(因为硬件与软件不同,写了这个指令之后不一定马上生效)

初始化页表并开启MMU

  • 将Kernel代码映射到低地址段(和物理地址相同)和高地址段两份

在这里插入图片描述
这里实际上做了两件事:将Kernel的代码同时映射到了低的地址段和高的地址段
这是为了方便启动的时候可以在两个地方同时访问,但是结束后会将低的地址段去掉

进入Kernel

  • 跳转到Kernel的main函数
    在这里插入图片描述
    • main函数中是对一些功能与核的初始化
      初始化UART、Exception vector、内存、锁、调度器、线程,再把其他的核启动起来,把pmu(相关的性能控制单元)设置起来
      在这里插入图片描述
  • 真正进入ChCore
  • 开启OS的各种服务

X86-64的启动过程

(与arm流程差不多)

  • 为了向前兼容,内核启动中存在兼容性工作
  • 通过段寄存器,进行模式转换(兼容需要)
    • 实模式(16-bit)
    • 保护模式(32-bit)
    • IA32E(64bit)
  • 由于实模式的内存限制(64KB),再将内核代码按段拷贝到内存中
  • 进入内核

硬件模拟

现在模拟器实现,有需要再加载硬件

使用模拟器进行开发

用软件模拟硬件行为

  • ARM模拟器
    • 仅用软件完全实现硬件行为
    • 如同泡在真正的ARM硬件上
      在这里插入图片描述

模拟CPU

无限循环,根据opcode进行相应操作
在这里插入图片描述

模拟寄存器

在这里插入图片描述

模拟内存

在这里插入图片描述

模拟设备

  • 硬盘:host OS的文件
  • VGA显示:host OS的显示窗口
  • 键盘:host OS的键盘API
  • 时钟芯片:host OS的时钟

物理内存与CPU缓存

  • 为什么要引入CPU缓存
    相比于CPU处理的速度,内存访问速度非常慢:一条算术运算指令可能只需要一个or几个时钟周期,而访存指令可能需要上百个时钟周期
    为了降低CPU访存的开销,现在的CPU中引入缓存,用于存放一部分物理内存中的数据

缓存结构

CPU缓存由若干个缓存行(cache line)组成的

  • 每个缓存行包括:
    • 一个有效位(valid bit):表示其是否有效
    • 一个标记地址(tag address):标识其对应的物理地址
    • 一些其他的状态信息

通常CPU以缓存行(常见是64字节)为单位把物理内存中的数据读取到CPU缓存中/写回物理内存
缓存地址在逻辑上可以分为:Tag、Index(set)、Offset三段

  • 组(Set)与路(Way)是CPU缓存的经典概念
    • 组:物理地址中的Set段能表示的最大数目(如Set段8位,则对应CPU缓存的组数为28 =256)
    • 路:同一组(Set段相等)下支持的最大的Tag数->同一组下的缓存行数目

缓存寻址

  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值