原创翻译,转载请注明出处。

arm64的异常模型由一组异常级别(EL0-EL3)组成。EL0,EL1有安全模式和非安全模式的区别。EL2是虚拟机管理级别并且只有非安全模式。EL3是最高优先级并且只存在安全模式中。
为了描述方便,下面将使用术语“boot loader”来简化所有执行在cpu将控制权转交给内核之前的软件的称呼。这里包含了安全监视器(secure monitor)和虚拟机管理器(hypervisor)的代码,或者可能是少量用来准备一个最小的启动环境的指令。
基本上,boot loader至少提供以下几个功能:

  • 安装与初始化物理内存
  • 安装设备树
  • 解压内核镜像
  • 启动内核镜像

1、安装与初始化物理内存
boot loader需要初始化物理内存,内核将使用这些内存来存储volatile类型的数据。这个是与机器有关的,可能使用了内部算法自动的定位并取得物理内存的大小,

或者可能是机器有关内存方面的特性,也可能是boot loader设计者知道的获取内存某种方法。(囧)

2、安装设备树
dtb(device tree blob)必须位于8-BYTE对齐的位置并且不能超过2MB的大小。因为dtb会被映射到最大2MB的缓存块上,它不能放在任何映射了特定属性的2M区域内。
注意,在内核4.2以前,要求将DTB放在内核镜像里以text_offset为起始位置的512M区域内。

3、解压内核镜像(这个是可选的)
arm64(aarch64)的内核当前并不提供自解压功能,因此需要解压在boot loader里完成(比如gzip格式)。如果boot loader不支持解压,可以使用不压缩的镜像来启动。

4、启动内核镜像
解压后的内核镜像包含64byte的头,头结构定义如下:

u32 code0;                           /* Executable code */
u32 code1; /* Executable code */
u64 text_offset; /* Image load offset, little endian */
u64 image_size; /* Effective Image size, little endian */
u64 flags; /* kernel flags, little endian */
u64 res2 = 0; /* reserved */
u64 res3 = 0; /* reserved */
u64 res4 = 0; /* reserved */
u32 magic = 0x644d5241; /* Magic number, little endian, "ARM\x64" */
u32 res5; /* reserved (used for PE COFF offset) */

头结构说明:

(1)、在内核3.17版本以前,所有字段都是小端字节序,除非有特别说明。
(2)、code0/code1 是为了响应 stext 分支。
(3)、如果以EFI(可扩展固件接口 Extensible Firmware Interface)启动,code0/code1一开始就会被跳过。res5是指PE头和有EFI入口点(efi_stub_entry)的PE头的偏移。当efi完成了它的工作,就会跳转到 code0 的位置继续正常的启动流程。
(4)、在内核3.17版本以前,text_offset字段的字节序是不确定的。举个栗子,在内核字节序里,image_size 为0,text_offset为 0x80000。如果 image_size 是一个非0值,必须注意了,这时image_size 是小端字节序。如果 image_size 为0,那么text_offset可以认为是 0x80000。
(5)、flags 字段(内核3.17版本引入的)是一个小端字节序的64bit字段,它的组成如下:

Bit 0: 内核字节序标识,
1是大端, 0是小端;
Bit 1-2:内核页的大小,
0 - 表示未说明,1 - 表示4K大小,2 - 16K,3 - 64K;
Bit 3:内核物理布局,
0 - 基地址2MB对齐并且基地址应该离DRAM的基地址越可能的近,因为是线性映射,所以内存地址低于它的不能访问。
1 - 基地址2MB对齐,可以位于物理内存的任何地方。
Bit 4-63:保留字段。

(6)如果 image_size 为0,在内核镜像启动结束之后,bootloader应提供尽量多的空闲内存给内核使用。这个空间的数量会随着不同的特性变化,实际上是没有明确的限制的。
内核镜像位于任何可用的 text_offset 大小的字节数的内存基地址上,这个地址必须是2MB对齐的。2MB对齐的基地址与内核镜像起始位置这之间的区域对内核是没有特别的意义的,可以用作它用。

在内核镜像起始的位置起,至少 image_size 大小的字节数必须是空闲的,以供内核使用。

注意:在内核4.6版本之前不能使用低于镜像大小的物理偏移的内存,所以推荐镜像放在离物理内存地址起始位置尽可能近的地方。
如果 initd/initramfs 在启动的时候传递给了内核,它必须整个属于1GB对齐的物理内存窗口到32GB大小之间,以全部覆盖内核镜像为好。
任何描述给内核的内存(包括低于镜像起始地址的),如果没有标记为保留的(dtb里的 /memreserve指定)将被内核认为是可以使用的。

在跳转到内核之前,下面的条件必须满足:
(1)禁用所有的具有DMA能力的设备,这样内存就不会被伪造的网络数据包或者硬盘数据污染。这将节省你大量的调试时间。
(2)主CPU的通用寄存器设置:

 x0 = dtb在系统内存的物理地址
x1 = 0 (保留给以后使用)
x2 = 0 (保留给以后使用)
x3 = 0 (保留给以后使用)

(3)CPU模式
所有的中断都必须在 PSTATE.DAIF (Debug,SError,IRQ,FIQ) 中设置掩码位。CPU必须处于EL2(推荐模式,方便虚拟化扩展访问)或者非安全模式的EL1模式中。

(4)Caches,MMUs
MMU必须关闭。
指令缓存可以开启或关闭。
对应于内核镜像的地址范围应该清理成PoC(PoC不知道是啥)。要使能系统缓存或者其他一致性主缓存,要求缓存维护通过VA,而不是 set/way 操作。
系统缓存 (依赖体系结构的缓存维护通过VA操作的)必须被配置并且使能,而不依赖体系结构通过VA维护的系统缓存必须禁止。

(5)定时器
CNTFRQ 必须对定时器频率是可编程的,并且CNTVOFF必须对在所有CPU上具有一致性的值是可编程的。如果进入内核时是在EL1模式,CNTHCTL_EL2 必须有EL1PCTEN (bit 0)设置可用。

(6)一致性
所有CPU通过内核启动必须是相同一致的内核入口的一部分。这将要求“IMPLEMENTATION DEFINED”的初始化来使能每个CPU来接收维护操作。

(7)系统寄存器
所有可写的系统寄存器在这内核镜像将要进入的异常级别(EL)必须在一个更高的异常级别(EL)通过软件初始化,来防止在一个未知的状态执行。

在一个有GICv3的中断控制器的系统可以使用v3模式:

1、如果是 EL3 :
ICC_SRE_EL3.Enable (bit 3) 必须初始化为 0b1.
ICC_SRE_EL3.SRE (bit 0) 必须初始化为 0b1.
2、内核是在 EL1 :
ICC.SRE_EL2.Enable (bit 3) 必须初始化为 0b1
ICC_SRE_EL2.SRE (bit 0) 必须初始化为 0b1.
3、DT或者ACPI表必须在GICv3中断控制器中。

上述的CPU模式,缓存,MMU,定时器,一致性,系统寄存器对应所有的CPU,所有CPU必须在相同异常级别进入内核。

bootloader在进入内核(每个cpu)都有如下规则:
(1)主CPU直接跳转到内核镜像的第一条指令。dtb传给每个CPU必须包含“enable-method”属性,这个属性在下面会描述。
  bootloader会生成这些设备树的属性并在内核入口之前插入到二进制执行文件中。

(2)带有“spin-table”使能方法的CPU必须有一个“cpu-release-addr”的属性节点。这个属性标识符以64bit自然对齐并在内存中初始化为0。
   这些CPU在内核之外的保留的内存区域(dtb里的 /memreserve/ 的指定区域)空转,并轮询“cpu-release-addr”地址,该地址也在保留区域内。

“wfe”指令可以用来插入减少这种busy-loop的开销,并且主CPU会发出“sev”(嘛东西。)。 当读取“cpu-release-addr”返回一个非0值,这个CPU必须跳转到这个值的地址。

这个值就是一个简单的64bit的小端的数值,所有这些cpu必须转换成它自己的原生字节序之后才能跳转过去。
 
(3)具有“psci”的使能方法的CPU应该停留在内核之外的保留内存区域。内核会发出“CPU_ON”的调用来将CPU带入内核。
  设备树应该包含一个“psci”节点。可以参考Documentation/devicetree/bindings/arm/psci.txt。

(4)从CPU上的通用寄存器设置:

x0 = 0 (reserved for future use)
x1 = 0 (reserved for future use)
x2 = 0 (reserved for future use)
x3 = 0 (reserved for future use)

Linux arm64内核启动的更多相关文章

  1. Linux - 修改内核启动顺序及删除无用内核

    现象: CentOS7开机启动界面显示多个内核选项 原因: 正常情况下,有两个启动项,一个是"正常启动",另一个是"救援模式启动"(rescue). 如果启动项 ...

  2. Linux内核启动过程概述

    版权声明:本文原创,转载需声明作者ID和原文链接地址. Hi!大家好,我是CrazyCatJack.今天给大家带来的是Linux内核启动过程概述.希望能够帮助大家更好的理解Linux内核的启动,并且创 ...

  3. linux内核启动以及文件系统的加载过程

    Linux 内核启动及文件系统加载过程 当u-boot 开始执行 bootcmd 命令,就进入 Linux 内核启动阶段.普通 Linux 内核的启动过程也可以分为两个阶段.本文以项目中使用的 lin ...

  4. Linux内核启动logo

    之前在分析samsung的fb驱动代码的时候,其中有一段代码是处理内核logo显示相关的,今天就内核logo这个话题来聊一聊! 一.处理内核logo显示相关的代码在哪? 回到samsung的fb驱动代 ...

  5. Linux内核启动分析过程-《Linux内核分析》week3作业

    环境搭建 环境的搭建参考课件,主要就是编译内核源码和生成镜像 start_kernel 从start_kernel开始,才真正进入了Linux内核的启动过程.我们可以把start_kernel看做平时 ...

  6. 【转载】linux内核启动android文件系统过程分析

    主要介绍linux 内核启动过程以及挂载android 根文件系统的过程,以及介绍android 源代码中文件系统部分的浅析. 主要源代码目录介绍Makefile (全局的Makefile)bioni ...

  7. Linux内核启动

    Linux内核启动过程概述 Linux的启动代码真的挺大,从汇编到C,从Makefile到LDS文件,需要理解的东西很多.毕竟Linux内核是由很多人,花费了巨大的时间和精力写出来的.而且直到现在,这 ...

  8. linux内核启动笔记

    一. 1.解压    tar xjf linux-2.6.22.6.tar.bz2 2.打补丁  patch -p1 < ../linux-2.6.22.6_jz2440.patch 3.配置 ...

  9. 通过从代码层面分析Linux内核启动来探知操作系统的启动过程

    通过从代码层面分析Linux内核启动来探知操作系统的启动过程 前言说明 本篇为网易云课堂Linux内核分析课程的第三周作业,我将围绕Linux 3.18的内核中的start_kernel到init进程 ...

随机推荐

  1. TCP套接字

    端口的概念 每个电脑一根网线,但是你挂着QQ的同时还可以浏览网页.两个不同应用的数据在同一根网线里是如何传输的呢?根据七层互联网模型,这个功能由运输层(TCP是运输层主要协议)实现.怎么实现呢,在网络 ...

  2. cc++面试------17道经典面试题目分析

    以下是C/C++面试题目,共计17个题目,其中涵盖了c的各种基础语法和算法, 以函数接口设计和算法设计为主.这17个题目在C/C++面试方面已经流行了多 年,大家需要抽时间掌握好,每一个题目后面附有参 ...

  3. P1247 取火柴游戏

    题目描述 输入k及k个整数n1,n2,-,nk,表示有k堆火柴棒,第i堆火柴棒的根数为ni:接着便是你和计算机取火柴棒的对弈游戏.取的规则如下:每次可以从一堆中取走若干根火柴,也可以一堆全部取走,但不 ...

  4. 【TOJ 3005】Triangle(判断点是否在三角形内+卡精度)

    描述 Given the coordinates of the vertices of a triangle,And a point. You just need to judge whether t ...

  5. 爬虫——正则表达式re模块

    为什么要学习正则表达式 实际上爬虫一共就四个主要步骤: 明确目标:需清楚目标网站 爬:将所有的目标网站的内容全部爬下来 取:在爬下来的网站内容中去掉对我们没有用处的数据,只留取我们需要的数据 处理数据 ...

  6. notepad++实现python运行

    一.先确保windows电脑上先安装python解释器 方法参考:https://www.cnblogs.com/hepeilinnow/p/9727922.html 二.打开notepad++,写一 ...

  7. springmvc的类型转换器converter

    这个convter类型转换是器做什么用的? 他是做类型转换的,或者数据格式化处理.可以把数据在送到controller之前做处理.变成你想要的格式或者类型.方便我们更好的使用. 比如说你从前台传过来一 ...

  8. vuejs中的计算属性和监视

    计算属性 1.在computed属性对象中定义计算属性的方法,在页面上使用{{方法名}}来显示计算结果 2.通过getter/setter实现对属性数据的显示和监视 3.计算属性存在缓存,多次读取只执 ...

  9. 各种数据库分页语句整理以及Oracle数据库中的ROWNUM和ORDER BY的区别

    .oracle数据库分页 select * from (select a.*,rownum rc from 表名 where rownum<=endrow) a where a.rc>=s ...

  10. VS2017发布微服务到docker

    1.本文档以eShopOnContainers.sevices.identity为描述对象,并包含docker for windows的部分配置流程. 2.前置环境:win10操作系统.安装VS201 ...