用“芯”探核:基于龙芯的Linux内核探索解析
上QQ阅读APP看书,第一时间看更新

1.1 龙芯处理器简介

中央处理器(Central Processing Unit,CPU)分为复杂指令集计算机(Complex Instruction Set Computer,CISC)和精简指令集计算机(Reduced Instruction Set Computer,RISC)两大类。CISC具有指令集复杂而庞大、指令字不等长、寻址方式复杂、计算指令操作数可以是内存等特征,典型代表有X86。RISC具有指令集精简而高效、指令字等长、寻址方式简明、计算指令操作数必须是寄存器等特征,典型代表有ARM、MIPS和Power。CISC和RISC各有优劣,在发展过程中也并非井水不犯河水,而是互相吸收对方的优点。X86在内部早已实现RISC化(所谓微指令),而RISC也引入了单指令流多数据流(Single Instruction Multiple Data,SIMD)等功能比较强大但复杂的指令(所谓向量化)。

龙芯CPU属于无互锁流水阶段微型计算机(Microcomputer without Interlocked Pipeline Stage,MIPS)家族,是RISC精简指令集体系结构的一种,产品线包括龙芯1号(小CPU)、龙芯2号(中CPU)和龙芯3号(大CPU)3个系列。龙芯系列处理器由龙芯中科技术有限公司(以下简称龙芯中科)研发,产品以32位和64位单核及多核CPU为主,主要面向网络安全、高端嵌入式、个人电脑、服务器和高性能计算机等应用。

龙芯1号系列为32位处理器,采用GS132(单发射32位)或GS232(双发射32位)处理器核,实现了带有静态分支预测和阻塞Cache的乱序执行流水线,集成各种外围接口,形成面向特定应用的单片解决方案,主要应用于云终端、工业控制、数据采集、手持终端、网络安全、消费电子等领域。2011年推出的龙芯1A和龙芯1B具有接口功能丰富、功耗低、性价比高、应用面广等特点。除了SoC,龙芯1A还可以作为桥片(PCI南桥)使用。2013年和2014年相继推出的龙芯1C和龙芯1D分别针对指纹生物识别和超声波计量领域定制,具有成本低、功耗低、功能丰富、性能突出等特点。2015年研制的龙芯1H则针对石油钻探领域随钻测井应用设计,目标工作温度高达175℃。

龙芯2号系列处理器采用GS264(双发射64位)或GS464(四发射64位)高性能处理器核,实现了带有动态分支预测和非阻塞Cache的超标量乱序执行流水线,同时还使用浮点数据通路复用技术实现了定点的SIMD指令,集成各种外围接口,形成面向嵌入式计算机、工业控制、移动信息终端、汽车电子等应用的64位高性能低功耗SoC芯片。2006年推出的龙芯2E是最早进行产业化的处理器,其主要产品是福珑迷你计算机。2008年推出的龙芯2F经过近几年的产业化推广,目前已经实现规模应用,其产品包括福珑迷你计算机、逸珑上网本计算机以及梦珑一体机等。集成度更高的龙芯2H于2013年推出,可作为独立SoC芯片,也可作为龙芯3号的桥片使用。目标为安全、移动领域的龙芯2K1000于2017年量产。

龙芯3号系列处理器基于可伸缩的多核互联架构设计,在单个芯片上集成多个GS464(四发射64位)、GS464E(增强型四发射64位)或GS464V(带向量扩展的增强型四发射64位)高性能处理器核以及大量的2级Cache,还通过高速存储和I/O接口实现多芯片的互联以组成更大规模的高速缓存一致的非均匀内存访问(Cache-Coherent Non-Uniform Memory Access,CC-NUMA)系统。龙芯3号面向高端嵌入式计算机、桌面计算机、服务器、高性能计算机等应用。2009年底推出四核龙芯3A1000。2011年推出65 nm的八核龙芯3B1000。2012年推出采用32 nm工艺设计的性能更高的八核龙芯3B1500,其最高主频可达1.5 GHz,支持向量运算加速,最高峰值计算能力达到192 GFLOPS(Giga Floating-point Operations Per Second,每秒10亿次浮点运算)。2015年,基于GS464E的新一代龙芯3A2000研制成功,在基本功耗与龙芯3A1000相当的情况下,综合性能提升2~4倍,已于2016年实现量产。工艺升级的新一代处理器是龙芯3A3000,已于2017年推出,性能较龙芯3A2000提高50%。龙芯3A1000、3A2000、3A3000保持引脚兼容,硬件上可以直接替换。全新的微结构升级的GS464V处理器核以及基于GS464V的龙芯3A4000已于2019年研制成功,增加了位向量指令集,在龙芯3A3000的基础上性能再次大幅提高。

MIPS体系结构的发展经历了MIPS I、MIPS II、MIPS III、MIPS IV、MIPS V(没有实现过)、MIPS R1、MIPS R2、MIPS R3、MIPS R5和MIPS R6(注意没有MIPS R4)等许多个版本,这些指令集版本的关系如图1-1所示。

图1-1 各版本MIPS指令集的关系

在传统版本发展阶段(MIPS I→MIPS II→MIPS III→MIPS IV),每一代指令集都是前一代的超集。其中,MIPS I和MIPS II只有32位,MIPS III和MIPS IV包括32位和64位。MIPS V虽然也是MIPS IV的超集,但只有规范定义,没有具体的处理器实现。

现代版本发展阶段改变了命名方式,在MIPS IV的基础上定义MIPS R1,其完整版叫MIPS64 R1,32位子集叫MIPS32 R1。后续的MIPS R1→MIPS R2→MIPS R3→MIPS R5的发展历程也是逐渐扩充的,但MIPS R6放弃了兼容,不再是以前版本的超集。

龙芯1号系列的各个型号处理器与MIPS32 R2(MIPS R2的32位版本)兼容;龙芯2E、2F与MIPS III兼容;龙芯2G、2H、3A1000、3B1000、3B1500与MIPS64 R1(MIPS R1的64位版本)兼容;龙芯3A2000、3A3000与MIPS64 R2(MIPS R2的完整版)兼容。注意,以上这些MIPS版本指的是体系结构(指令集ISA),并不是具体的CPU设计。MIPS公司早期的“官方公版处理器”主要有R2000、R3000、R4000、R6000、R8000、R10000、R12000、R16000等系列,而现在的MIPS公司主要是对设计授权,不再自己生产CPU。龙芯处理器在指令集以外的部分的许多设计跟R4000比较相似(如时钟源、Cache、MMU、FPU的设计等),因此在本书的解析中许多代码都是使用和R4000(代码缩写为r4k)相同的版本。

关于MIPS指令集和特权级的具体介绍本书不详细展开,读者如有需要请查阅MIPS官方文档MIPS Architecture For Programmers,该文档一共有3卷。

1.1.1 龙芯3号功能特征

本书的重点是计算机类应用,因此主要关注龙芯3号。目前已经得到大规模应用的龙芯3号处理器包括四核3A1000、八核3B1500、四核3A2000、四核3A3000和四核3A4000共5款。龙芯3号的整体架构基于两级互联实现,以四核处理器为例,其结构如图1-2所示。

图1-2 龙芯3号的整体架构

龙芯处理器支持通过HT控制器实现多路互联,多路互联意味着一台计算机上有多个处理器芯片(多个物理CPU)。处理器芯片内部集成内存控制器,这就意味着每个物理CPU有自己的“本地内存”,同时又能通过HT控制器互联总线访问其他物理CPU的“远程内存”。这种组织架构就是非均匀内存访问(Non-Uniform Memory Access,NUMA),即每个自带内存控制器的独立单元(传统上就是一个物理CPU)就是一个NUMA节点。如果硬件负责维护节点间的高速缓存一致性,就叫高速缓存一致的非均匀内存访问架构(Cache-Coherent Non-Uniform Memory Access,CCNUMA)。龙芯3号的NUMA是一种CC-NUMA,每个NUMA节点由4个CPU核组成,四核处理器(龙芯3A)包含1个NUMA节点,八核处理器(龙芯3B)包含2个NUMA节点。龙芯3号每个NUMA节点包含4个CPU核,每个CPU核有自己的本地Cache(一级Cache,L1 Cache),节点内的4个CPU核共享二级Cache(分体式共享,分为4个模块但作为整体被CPU核共享)。

NUMA虽然在内存访问上存在不均匀性,但在运行过程中每个处理器的地位是对等的,因此依旧属于对称多处理器(Symmetric Multi-Processor,SMP)。或者说,广义的SMP系统既包括均匀内存访问的对称多处理器(UMA-SMP),也包括非均匀内存访问的对称多处理器(NUMA-SMP)。与SMP系统相对的是单处理器(Uni-Processor,UP)系统。

龙芯3号每个节点的第一级互联采用6×6的交叉开关,用于连接4个CPU核(作为主设备)、4个二级Cache模块(作为从设备)以及2个I/O端口(每个端口使用一个Master主设备和一个Slave从设备)。一级互联开关连接的每个I/O端口连接一个16位的HT控制器,每个16位的HT端口还可以作为两个8位的HT端口使用。HT控制器通过一个DMA控制器和一级交叉开关相连,DMA控制器负责I/O端口的DMA控制并负责片间一致性的维护。龙芯3号的DMA控制器还可以通过配置实现预取和矩阵转置或搬移。

龙芯3号每个节点的第二级互联采用5×4的交叉开关,连接4个二级Cache模块(作为主设备)。从设备一方则包括两个DDR2/3内存控制器、本地的低速或高速I/O控制器(包括PCI、LPC、SPI、UART等)以及芯片内部的配置寄存器模块。

在交叉开关上面,所谓主设备,就是主动发起访问请求的主控方;所谓从设备,就是被动接受访问请求并给出响应的受控方。龙芯3号的两级交叉开关都采用读写分离的数据通道,数据通道宽度为128位,工作在与处理器核相同的频率,用以提供高速的片上数据传输。

由于基于龙芯3号可扩展互联架构,因此在组建NUMA时,四核龙芯3A可以通过HT端口连接构成2芯片八核的SMP结构或者4芯片16核的SMP结构。同样,八核龙芯3B也可以通过HT端口连接构成2芯片16核的SMP结构。

下面根据主要型号的发展顺序简单介绍龙芯3号的功能特征。

(一)龙芯3A1000

龙芯3A1000是一个配置为单节点四核的处理器,采用65 nm工艺制造,最高工作主频为1 GHz,主要技术特征如下。

○ 片内集成4个64位的四发射超标量GS464高性能处理器核。

○ 每个核的私有一级Cache包含64 KB指令Cache和64 KB数据Cache。

○ 片内集成4 MB的分体共享二级Cache(由4个体模块组成,每个体模块容量为1 MB)。

○ 通过目录协议维护多核及I/O DMA访问的Cache一致性。

○ 片内集成2个64位400 MHz的DDR2/3控制器。

○ 片内集成2个16位800 MHz的HyperTransport控制器。

○ 每个16位的HT端口拆分成两个8路的HT端口使用。

○ 片内集成32位100 MHz PCIX/66 MHz PCI控制器。

○ 片内集成1个LPC、2个UART、1个SPI、16路GPIO接口。

(二)龙芯3B1500

龙芯3B1500是一个配置为双节点的八核处理器,采用32 nm工艺制造,最高工作主频为1.2 GHz(低电压版)/1.5 GHz(高电压版),主要技术特征如下。

○ 片内集成8个64位的四发射超标量GS464高性能处理器核。

○ 每个核的私有一级Cache包含64 KB指令Cache、64 KB数据Cache和128 KB牺牲Cache。

○ 每个处理器核频率单独可设。

○ 片内集成8 MB的分体共享二级Cache(由8个体模块组成,每个体模块容量为1 MB)。

○ 通过目录协议维护多核及I/O DMA访问的Cache一致性。

○ 片内集成2个64位667 MHz的DDR2/3控制器,支持DDR3-1333。

○ 片内集成2个16位800 MHz的HyperTransport控制器,最高支持1600 MHz总线。

○ 每个16位的HT端口可以拆分成两个8路的HT端口使用。

○ 片内集成32位33 MHz PCI控制器。

○ 片内集成1个LPC、2个UART、1个SPI、16路GPIO接口。

(三)龙芯3A2000

龙芯3A2000是龙芯3A1000四核处理器的结构升级版本,封装引脚与龙芯3A1000兼容。龙芯3A2000是一个配置为单节点四核的处理器,采用40 nm工艺制造,最高工作主频为1 GHz,主要技术特征如下。

○ 片内集成4个64位的四发射超标量GS464E高性能处理器核。

○ 每个核的私有一级Cache包含64 KB指令Cache、64 KB数据Cache和256 KB牺牲Cache。

○ 片内集成4 MB的分体共享二级Cache(由4个体模块组成,每个体模块容量为1 MB)。

○ 通过目录协议维护多核及I/O DMA访问的Cache一致性。

○ 片内集成2个64位带ECC、667 MHz的DDR2/3控制器。

○ 片内集成2个16位1.6 GHz的HyperTransport控制器。[1]

○ 每个16位的HT端口拆分成两个8路的HT端口使用。

○ 片内集成32位33 MHz PCI控制器。

○ 片内集成1个LPC、2个UART、1个SPI、16路GPIO接口。

相比龙芯3A1000,其主要改进如下。

○ 处理器核结构全面升级,引入双TLB设计(VTLB+FTLB)和SFB(Store Fill Buffer)。

○ 内存控制器HT控制器结构、频率全面升级。

○ 内部互连结构和外部扩展互连结构均全面升级。

○ 支持SPI启动功能。

○ 支持全芯片软件频率配置。

○ 全芯片的性能优化提升,在主频不变的情况下性能提高2~4倍。

(四)龙芯3A3000

龙芯3A3000是龙芯3A2000四核处理器的工艺升级版本,封装引脚与龙芯3A1000、3A2000兼容。龙芯3A3000是一个配置为单节点四核的处理器,采用28 nm工艺制造,最高工作主频为1.5 GHz,主要技术特征如下。

○ 片内集成4个64位的四发射超标量GS464E高性能处理器核。

○ 每个核的私有一级Cache包含64 KB指令Cache、64 KB数据Cache和256 KB牺牲Cache。

○ 片内集成8 MB的分体共享二级Cache(由4个体模块组成,每个体模块容量为2 MB)。

○ 通过目录协议维护多核及I/O DMA访问的Cache一致性。

○ 片内集成2个64位带ECC、667 MHz的DDR2/3控制器。

○ 片内集成2个16位1.6 GHz的HyperTransport控制器[2]

○ 每个16位的HT端口拆分成两个8路的HT端口使用。

○ 片内集成32位33 MHz PCI控制器。

○ 片内集成1个LPC、2个UART、1个SPI、16路GPIO接口。

相比龙芯3A2000,其主要改进如下。

○ 工艺升级,主频提升至1.5 GHz。

○ 二级Cache容量由4 MB增加到8 MB。

○ 全芯片的性能优化,性能提升50%左右。

(五)龙芯3A4000

龙芯3A4000是一款四核龙芯处理器,采用28 nm工艺制造,稳定工作主频为1.8~2.0 GHz,主要技术特征如下。

○ 片内集成4个64位的四发射超标量GS464V高性能处理器核,支持向量运算扩展指令。

○ 每个核的私有一级Cache包含64 KB指令Cache、64 KB数据Cache和256 KB牺牲Cache。

○ 片内集成8 MB的分体共享二级Cache(由4个体模块组成,每个体模块容量为2 MB)。

○ 通过目录协议维护多核及I/O DMA访问的Cache一致性。

○ 片内集成2个64位带ECC、800 MHz的DDR3/4控制器。

○ 片内集成2个16位1.6 GHz的HyperTransport控制器。

○ 每个16位的HT端口拆分成两个8路的HT端口使用。

○ 片内集成2个I2C、1个UART、1个SPI、16路GPIO接口。

龙芯3A4000的顶层结构设计在龙芯3A2000和3A3000的基础上进行了较大幅度的优化,其主要改进如下。

○ 继续采用双TLB设计,VTLB保持64项,FTLB从1024项增加到2048项。

○ 调整了片上互联结构,简化了地址路由,I/O模块间互联采用RING结构。

○ 优化了HT控制器的带宽利用率与跨片延时。

○ 优化了内存控制器结构,增加了DDR4内存的支持,并支持内存槽连接加速卡。

○ 规范了配置寄存器空间与访问方式,引入了CSR配置寄存器访问机制。

○ 优化了中断控制器结构,支持向量中断分发机制。

○ 增加了8路互联支持。

○ 全芯片的性能优化提升,在主频不变的情况下性能提高约50%。

关于龙芯3号系列处理器的更多细节可参阅各型号的《龙芯处理器用户手册》,每种型号均有上下两册。

注意:

龙芯3B1500的处理器核带有向量扩展指令集,所以早期也曾称为GS464V,但该扩展指令集存在较多的缺陷,实际中并未使用起来。因此,现在一般也将龙芯3B1500的处理器核称为GS464(同龙芯3A1000),而将带有新的向量扩展指令集的龙芯3A4000的处理器核称为GS464V。

1.1.2 龙芯3号处理器核

在逻辑上,一个龙芯3号处理器核包括主处理器、协处理器0、协处理器1、协处理器2、一级Cache、SFB和TLB等多个组成部分。

主处理器:实现整数运算、逻辑运算、流程控制等功能的部件,包括32个通用寄存器(General Purpose Registers,GPR)以及Hi/Lo两个辅助寄存器。

协处理器0(CP0):名称是系统控制协处理器,负责一些跟特权级以及内存管理单元(Memory Management Unit,MMU)有关的功能,至少包括32个CP0寄存器。

协处理器1(CP1):名称是浮点运算协处理器(FPU),负责单精度/双精度浮点运算,包括32个浮点运算寄存器(FPR)和若干个浮点控制与状态寄存器(FCSR)。

协处理器2(CP2):名称是多媒体指令协处理器,负责多媒体指令(MMI指令)的运算,共享FPU的运算寄存器。

一级Cache(L1 Cache):包括64 KB指令Cache(I-Cache)、64 KB数据Cache(D-Cache),从龙芯3B1500开始还包括256 KB牺牲Cache(V-Cache)。注意,二级Cache不属于处理器核的组成部分,而是被多个处理器核共享的。I-Cache和D-Cache采用VIPT组织方式,4路组相联;V-Cache采用PIPT组织方式,16路组相联。

SFB:全称Store Fill Buffer,是从龙芯3A2000开始引入的功能部件,可以大幅优化访存性能。SFB位于寄存器和一级Cache之间,在功能上可以把SFB理解为零级Cache(L0 Cache),但是只有数据访问会经过SFB,取指令直接访问一级Cache。

TLB:全称Translation Lookaside Buffer,即快速翻译查找表,是为了加速页表访问而引入的一种高速缓存(专属于页表的Cache)。龙芯3号的主TLB不分指令和数据,因此统称为JTLB(另有软件透明的uTLB,分为指令ITLB和数据DTLB,整个uTLB是JTLB的子集,类似于一级Cache和二级Cache的关系)。龙芯3A1000~3B1500每个核有64项JTLB(页大小可变因此也叫VTLB);龙芯3A2000、3A3000除64项VTLB以外还有1024项页大小固定的FTLB;龙芯3A4000除64项VTLB以外还有2048项页大小固定的FTLB。VTLB和FTLB同属于JTLB,前者采用全相联方式,后者采用8路组相联方式。

注意:

根据MIPS处理器规范,一个处理器核总共可以有4个协处理器,但是龙芯3号只设计了3个,因此没有CP3。

MIPS处理器核的字节序格式既可以使用大尾端(Big-Endian)也可以使用小尾端(LittleEndian),但龙芯只支持小尾端格式。

下面简单介绍龙芯3号处理器核的一些内部特征细节,主要包括通用寄存器、CP0寄存器和指令集。

(一)通用寄存器

龙芯处理器核有32个通用寄存器(GPR),在64位模式下,GPR字长均为64位;在32位兼容模式下,GPR只有低32位可用。所谓通用寄存器,就是可以用作任意用途,不过0号GPR是一个特殊的通用寄存器,其值永远为0。虽然在硬件设计上GPR的用途没有特意规定(GPR0除外),但是在软件使用上遵循一定的约定,这种约定就是应用二进制接口(Application Binary Interface,ABI)。MIPS处理器有3种常用的ABI:O32、N32和N64(O代表Old,旧的;N代表New,新的),其主要特征如下。

O32:只能用32位操作数指令和32位GPR,C语言数据类型的char、short、int、long和指针分别为8位、16位、32位、32位和32位。

N32:可以用64位操作数指令和64位GPR,C语言数据类型的char、short、int、long和指针分别为8位、16位、32位、32位和32位。

N64:可以用64位操作数指令和64位GPR,C语言数据类型的char、short、int、long和指针分别为8位、16位、32位、64位和64位。

除了上述数据格式的差别,3种ABI的另一个重要区别是32个通用寄存器的使用约定不一样,具体如表1-1所示。在内核代码中寄存器编号采用$n的形式来表示第n个通用寄存器,即GPRn;寄存器名称也叫助记符,用于表征其功能,在内核代码里面通常使用小写字母,但在文档资料介绍中通常使用大写字母。

表1-1 龙芯(MIPS)处理器的寄存器使用约定

续表

GPR0:ZERO,它的值永远为0,之所以要设置这个寄存器,是因为RISC使用定长指令(MIPS的标准指令字长度为32位),如果没有零值寄存器,就无法把32位/64位的零操作数编码到指令中。

GPR1:AT,是保留给汇编器使用的,用于合成宏指令。宏指令是那些处理器实际上不提供,而由汇编器利用多条指令合成的伪指令,比如加载任意立即数的li指令。

GPR31:RA,通常用来保存函数返回的地址。

V系列寄存器:用于保存函数的返回值。

A系列寄存器:用于传递函数的参数。N32/N64与O32相比有更多的A系列寄存器,因此更有利于使用寄存器而不是堆栈来传递参数。

S系列寄存器:函数调用时需要保存的寄存器。

T系列寄存器:可以随意使用的临时寄存器。更确切地说,S寄存器由被调用者负责保存/恢复,T寄存器由调用者负责保存/恢复(如果需要的话)。N32/N64与O32相比T系列寄存器更少,因为它们被用于A系列寄存器了。

K系列寄存器:保留给内核在异常处理时使用的,应用程序不应当使用。

GPR28:GP,是Global Pointer(全局指针)的缩写。由于存在大量的进程间共享,应用程序和动态链接库一般被设计成位置无关代码(Position Independent Code,PIC)。因此,全局变量(更一般地说是全局符号,包括全局函数和全局变量)往往不能通过一个确定的地址直接访问。为了解决这个问题,需要在每个链接单元中引入一个GOT(Global Offset Table),然后通过GP寄存器指向GOT表来间接访问这些全局变量。相比之下,内核通常链接在固定的地址(地址有关代码),因此不需要GOT表。新版的Linux内核也支持重定位,但一台电脑上只运行一个内核,没有共享问题,所以重定位内核可以使用地址修正法而不需要GOT表。内核的模块使用了位置无关代码,但内核模块同样没有共享问题;所以在加载模块的时候地址即可唯一确定,因而即便是跨模块的全局符号访问也可以通过EXPORT_SYMBOL()等方法来解决。总而言之,内核里对全局函数和全局变量的访问不需要使用GP,因此在内核中GP通常用来指向当前进程的thread_info地址,这样可以优化对当前进程本地数据结构的访问性能。

GPR29:SP,是Stack Pointer(栈指针)的缩写,主要用于访问局部变量(以及栈里面的其他数据)。

GPR30:FP,是Frame Pointer(帧指针)的缩写,用来辅助访问局部变量。FP不是必需的,在不需要FP的代码里面,可以当S8使用。

为了理解SP和FP,我们简单介绍一下龙芯3号(MIPS通用)的栈结构[3],如图1-3所示。

图1-3 龙芯3号的栈和栈帧

在大多数体系结构的设计中,堆是从低地址往高地址扩展,而栈是从高地址往低地址扩展。龙芯平台的栈设计也是从高地址往低地址扩展,其栈的起始点称为栈底(最高地址),在使用过程中动态浮动的结束点称为栈顶(最低地址)。除了保存局部变量以外,栈的最大作用是保存函数调用过程中的寄存器状态。因此,在函数逐级调用时,栈里面的内容是一段一段的,每一段称为一个栈帧。从高地址往低地址看,首先是父函数(当前函数的调用者)的栈帧,然后是当前函数的栈帧,再然后是子函数的栈帧,依此类推。每一个栈帧的内容是类似的:首先是函数返回地址,然后是需要保存的寄存器,再然后是当前函数用到的局部变量,最后是调用的子函数参数(最下层的叶子函数不需要参数空间)。在大多数情况下,函数调用时子函数的第一步就是调整栈指针SP(从父函数栈帧的最低地址调整到子函数栈帧的最低地址),然后在整个子函数活动时间内SP保持不变,因此引用局部变量时有一个固定不变的基地址。但是C语言运行时库里面可能会提供alloca()等函数用于在栈里面分配内存空间,这会导致活动期间SP发生变化。在这种情况下,为了方便引用局部变量就会引入FP;FP指向当前栈帧的最高地址并且在函数活动期间不发生变化。注意:MIPS的ABI约定优先使用寄存器传递参数,寄存器不够用时才会使用栈传递。但即便如此,栈帧结构里面依旧会给每个参数预留空间,栈帧里面这些不会被使用的参数空间称为影子空间(不管哪种ABI,前4个参数一定会通过A0~A3寄存器传递,所以栈帧参数空间里面前4个一定是影子空间)。

(二)CP0寄存器

系统控制协处理器(CP0)拥有至少32个寄存器。之所以说“至少”,是因为某些编号寄存器包含扩展的子寄存器。龙芯3A2000(以及更新的处理器)比龙芯3A1000、3B1000、3B1500拥有更多的扩展,其概述如表1-2所示。

表1-2 龙芯处理器CP0寄存器概览

续表

对于这些寄存器的具体功能,本章不一一展开,后续章节在必要的时候会专门讲解。

(三)指令集

MIPS作为一种RISC指令集,相对来说是比较精简的,指令名(助记符)也很有规律。下面分类介绍常用指令。

访存指令:LB、LBU、LH、LHU、LW、LWU、LD、SB、SH、SW、SD、LUI、LI、DLI、LA、DLA。

访存指令分为加载指令和存储指令两类,前者是将内存内容读到寄存器,后者是将寄存器内容写入内存。指令名的含义基本上遵循“操作类型—操作位宽—后缀”的规律。操作类型是L(Load,即加载)或S(Store,即存储);操作位宽为B、H、W或D,分别代表Byte(字节,8位)、Half-Word(半字,16位)、Word(字,32位)、Double-Word(双字,64位);后缀U表示加载的数是无符号整数,会对高位进行零扩展而不是符号扩展。例如,LHU表示加载一个16位无符号整数到寄存器,对高位部分进行零扩展。

但是,LUI、LI/DLI、LA/DLA不符合上述规律。LUI表示加载高半字立即数,即加载一个无符号16位立即数并左移16位。LI/DLI是宏指令,作用是加载一个任意32位/64位立即数到寄存器。LA/DLA也是宏指令,作用是加载一个符号(变量名或函数名)的32位/64位地址到寄存器。

计算指令:ADDI、ADDIU、DADDI、DADDIU、ADD、ADDU、DADD、DADDU、SUB、SUBU、DSUB、DSUBU、MULT、DMULT、MULTU、DMULTU、DIV、DDIV、DIVU、DDIVU、MFHI、MTHI、MFLO、MTLO。

计算指令指整数的加减乘除四则运算,指令名的含义基本上遵循“操作位宽一计算类型一后缀”的规律。操作位宽无D的表示32位操作数,有D的表示64位操作数;计算类型分加(ADD)、减(SUB)、乘(MULT)、除(DIV)4种;无后缀的表示两个源操作数都来自寄存器,后缀为I的表示一个源操作数来自寄存器而另一个源操作数是立即数,后缀为U的表示无符号操作数(实际含义是溢出时不产生异常),后缀IU则是I后缀和U后缀的组合。例如,DADDIU表示64位加法指令,一个加数来自寄存器而另一个加数为立即数,运算溢出时不产生异常。

两个标准字长的操作数做乘法往往会产生双倍字长的结果,比如32位操作数乘32位操作数的结果可能是64位操作数。因此,MIPS在32个通用寄存器之外专门设置了Hi寄存器和Lo寄存器,分别用来保存乘法运算结果的高位字和低位字。在进行除法运算时,Lo寄存器保存商,Hi寄存器保存余数。MFHI/MTHI/MFLO/MTLO用于在通用寄存器和Hi/Lo两个辅助寄存器之间传递数据,MF指的是Move From,MT指的是Move To。顾名思义,MFHI就是将Hi寄存器中的数据传递到通用寄存器。

逻辑指令:AND、OR、XOR、NOR、ANDI、ORI、XORI。

4种基本运算:AND是逻辑与,OR是逻辑或,XOR是逻辑异或,NOR是逻辑或非。无后缀I的指令表示两个源操作数都来自寄存器,有后缀I的表示一个源操作数来自寄存器而另一个源操作数是立即数。

移位指令:SLL、SRL、SRA、ROTR、SLLV、SRLV、SRAV、ROTRV、DSLL、DSRL、DSRA、DROTR、DSLLV、DSRLV、DSRAV、DROTRV。

4种32位基本操作:SLL是逻辑左移(低位补充零),SRL是逻辑右移(高位补充零),SRA是算术右移(高位补充符号位),ROTR是循环右移(高位补充从低位移出的部分)。其他的可以以此类推,带D前缀的指令是64位操作数,无V后缀的是固定移位(移位的位数由立即数给出),有V后缀的是可变移位(移位的位数由寄存器给出)。

跳转指令:J、JR、JAL、JALR、B、BAL、BEQ、BEQAL、BNE、BNEAL、BLTZ、BLTZAL、BGTZ、BGTZAL、BLEZ、BLEZAL、BGEZ、BGEZAL。

前缀为J的指令表示绝对跳转(无条件跳转),跳转目标地址是相对PC所在地址段的256 MB边界的偏移;前缀为B的是相对跳转(有条件跳转,也叫分支指令),跳转目标地址是相对PC的偏移。J类跳转指令里面:无后缀R表示目标地址为立即数,有后缀R表示跳转目标为寄存器的值;无后缀AL表示普通跳转,有后缀AL表示链接跳转(自动保存返回地址到RA寄存器,用于函数调用)。B类分支指令里面:EQ表示跳转条件为相等(Equal),NE表示跳转条件为不相等(Not Equal),LT表示跳转条件为小于(Less Than),GT表示跳转条件为大于(Greater Than),LE表示跳转条件为小于或等于(Less Than or Equal),GE表示跳转条件为大于或等于(Greater Than or Equal),AL后缀表示链接跳转。B类分支指令里面有一部分是宏指令。例如,BGEZAL表示若源操作数大于或等于零,就执行相对链接跳转。

协处理器指令:MFC0、MTC0、DMFC0、DMTC0、MFC1、MTC1、DMFC1、DMTC1、MFC2、MTC2、DMFC2、DMTC2。

协处理器指令用于在通用寄存器和协处理器寄存器之间传送数据。MF表示Move From,MT表示Move To;无前缀D的指令表示操作32位协处理器寄存器,有前缀D的表示操作64位协处理器寄存器;后缀C0表示协处理器0,C1表示协处理器1,C2表示协处理器2。例如,DMFC0表示从协处理器0的64位寄存器传递一个数据到通用寄存器。

MMU相关指令:CACHE(高速缓存维护)、TLBP(TLB查询)、TLBR(读TLB项)、TLBWI(写TLB指定项)、TLBWR(写TLB随机项)。

特殊指令:SYNC(内存屏障)、SYSCALL(系统调用)、ERET(异常返回)、BREAK(断点)、EI(开中断)、DI(关中断)、NOP(空操作)、WAIT(暂停等待)。

浮点运算指令和龙芯扩展指令在内核中极少使用,此处不予介绍。有关寄存器和指令集的更多信息请参阅MIPS架构文档、龙芯处理器手册以及《MIPS体系结构透视》[1]

1.1.3 龙芯电脑基本结构

本书讲述的是基于龙芯平台的Linux内核,操作系统管理的不仅仅是处理器,而是整个计算机。那么,现在我们来了解一下龙芯电脑的基本结构。

传统的处理器仅仅指的是CPU核,而现代的处理器通常包括更多的功能。从处理器结构图(如图1-2所示)可以看出,除4个CPU核以外,龙芯处理器还包括本地私有一级Cache、共享二级Cache、两个内存控制器和两个HT控制器等重要结构。其中内存控制器用于连接内存,HT0控制器用于多个处理器芯片互联以构建CC-NUMA系统,HT1控制器用于连接芯片组进而挂接各种外围设备。

龙芯3号处理器可以跟多种芯片组搭配,比如RS690(北桥)+SB600(南桥)、RS780(北桥)+SB700(南桥)、RS880(北桥)+SB800(南桥)、SR5690(北桥)+SP5100(南桥)。以上芯片组都是由美国AMD公司出品,互相之间基本保持兼容,故以RS780为代表将它们统称为RS780机型。除了使用AMD芯片组之外,龙芯电脑还可以使用南北桥合一(合在一起称为桥片)的LS2H或LS7A,这两种芯片组都是龙芯中科自产。

在RS780机型中,龙芯的外围总线总体上是基于PCI-Express(以下简称PCI-E或PCIe)的,PCIe的根节点位于北桥芯片,下面可以挂接PCIe显卡(包括北桥内部的集成显卡)、PCIe声卡、PCIe网卡等高速设备。南桥芯片内部包含各种低速控制器,如SATA控制器、USB控制器、PCI控制器等,这些低速控制器本身是PCIe设备,其下挂接的则是各种相应的低速设备。使用RS780机型的龙芯电脑基本结构如图1-4所示。

图1-4 龙芯电脑内部结构框架图(使用RS780芯片组)

LS2H是第一款可以用于龙芯3号处理器的国产桥片,内含CPU核,因此也可以当SoC用(即龙芯2H)。LS7A则可以视为LS2H的升级改进版本,是第一款专门针对龙芯3号处理器设计的国产桥片(去掉了CPU核),在功能和性能上有了全面的提升,也是现在使用的主流型号。LS2H/LS7A机型与RS780机型相比没有太大的区别,仅仅是南桥芯片和北桥芯片集成在一起而已。使用LS2H/LS7A芯片组的龙芯电脑基本结构如图1-5所示。

从图1-4和图1-5的对比可以看出,除了将南北桥芯片集成在一起之外,LS2H/LS7A机型允许一些低速设备控制器不通过PCIe根,而是直接与HT控制器的内部总线相连接。比如在LS7A桥片中,RTC和I2C设备直接使用内部总线连接,而LPC、SPI等控制器则通过PCIe连接。在Linux内核中,直接通过内部总线连接的设备叫平台设备(platform device),因此内部总线也叫平台总线(platform bus)。也就是说,在LS2H/LS7A机型中,外围设备有PCIe总线设备(简称PCIe设备)和平台总线设备(简称平台设备)两大类。

根据PCIe总线规范,PCIe设备运行时是可探测的,因此同一套软件可以不做任何变化地应用于不同外设配置的机器上。然而,平台设备运行时却不可以探测,传统上这类设备只能在内核里面静态声明,因而影响可移植性。为了解决这个问题,现在比较常用的方法是设备树(DeviceTree)。设备树是对一台机器上所有平台设备信息的描述(实际上也可以描述其他类型的设备),集成在BIOS中并以启动参数的方式传递给Linux内核。如果BIOS没有传递设备树信息,则内核可以使用默认的设备树描述。在龙芯平台上,LS2H、LS7A和RS780这3种机型的默认设备树描述文件分别是arch/mips/boot/dts/loongson/loongson3_ls2h.dts、arch/ mips/boot/dts/loongson/loongson3_ls7a.dts和arch/mips/boot/dts/loongson/loongson3_rs780.dts。

图1-5 龙芯电脑内部结构框架图(使用LS2H/LS7A芯片组)

从龙芯电脑结构框架图(如图1-4和图1-5所示)中还可以看出,4个CPU核的一级Cache与2个HT控制器一样,都是二级Cache的主设备。龙芯处理器在硬件上可以通过目录协议[4]来维护CPU核间Cache一致性,以及CPU核与外设DMA之间的Cache一致性。这种不需要软件处理一致性问题的设计,大大方便了Linux内核的开发者。然而,为了满足一些特殊场合的要求,龙芯平台的Linux内核既支持一致性DMA(由硬件维护CPU核与DMA之间的一致性),也支持非一致性DMA(由软件维护CPU核与DMA之间的一致性)。注意:这里涉及的一致性是指空间一致性(coherency)而不是时序一致性(consistancy)。空间一致性指多个Cache副本之间的一致性,由龙芯CPU硬件负责维护;而时序一致性指多个处理器之间访存操作的顺序问题,通常需要软件和硬件协同解决(参考本书附录A.1内存屏障一节)。