信息时代的世界有着光鲜亮丽的外壳.docx
- 文档编号:6129036
- 上传时间:2023-01-04
- 格式:DOCX
- 页数:13
- 大小:27.97KB
信息时代的世界有着光鲜亮丽的外壳.docx
《信息时代的世界有着光鲜亮丽的外壳.docx》由会员分享,可在线阅读,更多相关《信息时代的世界有着光鲜亮丽的外壳.docx(13页珍藏版)》请在冰豆网上搜索。
信息时代的世界有着光鲜亮丽的外壳
信息时代的世界,有着光鲜亮丽的外壳。
Web2.0的时代,搜索引擎,SNS,流媒体...这绚丽多彩的一切,流光溢彩,仿佛云霓灿烂。
剥去这光鲜的外壳,深入到底层的世界。
内核,体系结构,编译器...完全是0和1组成的黑白世界,初涉之难免觉得味同嚼蜡。
但是,正如单一的白光在三棱镜下,会绽放出七彩的光谱,黑白的计算机底层世界,却有着魔法一般丰富的内涵。
当我们看穿华丽的GUI界面的伪装,探究到内核的奥秘时,我们会发现这是一个迷人的微观世界。
这个美丽新世界,最具魅力的是哪些呢?
体系结构。
它是计算机系统的基石。
它从根本上决定了这个系统的性能。
当程序中出现错误时,对体系结构的理解,能大大加快Bug的定位。
体系结构是底层世界的根基。
操作系统。
它是计算机系统的灵魂。
操作系统屏蔽了底层硬件的差异,为上层软件提供了统一的API。
多任务操作系统还可以让计算机并行处理不同的任务,巧妙的设计可以发挥CPU最大的潜力。
目前的最新操作系统版本,还可以支持多CPU并发处理。
操作系统内核是底层世界的统治者。
编译器。
编译器是体系结构的翻译官。
它能够将高级语言翻译成机器指令,与操作系统提供的API接口连接,开发出各种应用程序。
好的编译器还可以针对硬件做优化。
编译器是底层世界的一把金钥匙。
体系结构,操作系统,编译器支撑起的系统,如同三只足支撑的一只鼎。
操作系统中的经典之作,是Linux;对应的编译器是gcc系列工具链。
关于这些的著作早已汗牛充栋。
我想给大家介绍的,是体系结构皇冠上的明珠——MIPS。
英国科学家DominicSweetman(经典著作SeeMIPSRun的作者),称MIPS为“高效的RISC体系结构中最优雅的一种体系结构”。
它是一个双关语:
即是MicrocomputerwithoutInterlockedPipelineStages的缩写,同时又是MillionsofInstructionsPerSecond的缩写。
MIPS的处理单元是一个五级流水线:
InstructionFetch,Register&Decoder,ALU,Memory以及Writeback。
一开始的MIPS是为32位系统设计的,实际上,后续的64位扩展,也依然对32位的工作模式向下兼容着。
一如其他的RISC处理器,MIPS的每条指令长度是固定的32bit。
(因此,最长的局部跳转指令只能跳转2的26次方Byte,也就是2的24次方,16777216条指令)
MIPS有32个通用寄存器,编程者可以使用其中除$0外的所有寄存器暂存数据。
$0寄存器,在硬件上被设计为永远读出0。
我们可以用$0寄存器的此特性,实现一些技巧性的编程,譬如实现NOP操作。
MIPS本没有NOP指令,但由于对$0寄存器的写入实际上无意义,可以作为空操作使用。
事实上,编译器从高级语言(典型如C/C++)转换为MIPS汇编指令时,一般总是遵守一定的寄存器使用约定。
某些寄存器用来从函数中传入和传出参数,存储临时数据,另一些则起特殊作用,如保存调用函数时的指令地址,或作为堆栈指针等。
如果你使用汇编开发,理论上可以无视这点约定,但是,一般地,遵守这个约定,与人方便,也为己方便。
关于寄存器使用的约定,以后会展开论述。
一如所有的RISC处理器,MIPS没有CISC那样复杂多变的寻址方式,统一为Load/Store寻址。
任何载入和存储操作,都可写为如下形式:
lw$1,offset($2)
这条指令的操作符可以为Load或Store,一次Load/Store操作的范围可以为字/半字/字节(对应gcc的int,short和char)。
偏移量是一个带符号的16bit整数。
两个作为操作数的寄存器可以是任何通用寄存器。
(你可以向$0写入,但无任何意义,等同于空操作)。
对于64位模式下,也可以对double类型进行操作。
注意Load/Store都必须对应一个对齐的地址,否则会引发一个异常(Exception)。
MIPS支持最多4个协处理器。
协处理器CP0为CPU的控制协处理器,是体系结构中必须实现的。
CP1为浮点处理器。
CP2保留,各生产厂商往往用来实现一些自己的特色功能,例如RMI的FastMessageRing等。
CP3原本也是保留共扩展用,但很多MIPSIII和MIPSIV时代的扩展指令集使用了它。
对于CP0,我们会有专门的一段用来讨论。
为适应处理器向多核时代的演进,MIPS引入了多核操作必要的原子指令(Atomicoperation)、内存屏障(Barrier)等操作。
在SMP或AMP等多核架构中,这些指令是并行计算同步的保障。
二MIPS的内存映射
在32位MIPS体系结构下,最多可寻址4GB地址空间。
这4GB空间的分配是怎样的呢?
让我们看下面这张图:
+----------------------------------------------+
| |0xFFFFFFFF
| |
| |
| KernelSpaceMappedCached |
| |
| |
0xC0000000| |
+----------------------------------------------+
| |0xBFFFFFFF
|KernelSpaceUnmappedCached |
| |
0xA0000000+----------------------------------------------+
| |0x9FFFFFFF
|KernelSpaceUnmappedUncached |
| |
0x80000000+----------------------------------------------+
| |0x7FFFFFFF
| |
| |
| |
| UserSpace |
| |
| |
| |
| |
| |
0x00000000+----------------------------------------------+
Figure2-1MIPSLogicalAddressingSpace
上图是MIPS处理器的逻辑寻址空间分布图。
我们看到,2GB以下的地址空间,也就是从0x00000000到0x7FFFFFFF的这一段空间,为UserSpace,可以在UserMode下访问,当然,在KernelMode下也是可以访问的。
程序在访问UserSpace的内存时,会通过MMU的TLB,映射到实际的物理地址上。
也就是说,这一段逻辑地址空间和物理地址空间的对应关系,是由MMU中的TLB表项决定的。
从0x80000000到0xFFFFFFFF的一段为KernelSpace,仅限于KernelMode访问。
如果在UserMode下试图访问这一段内存,将会引发系统的一个Exception。
MIPS的 KernelSpace又可以划分为三部分。
首先是通过MMU映射到物理地址的1GB空间,地址范围从0xC0000000到0xFFFFFFFF。
这1GB空间可以用来访问实际的DRAM内存,可以为操作系统的内核所用。
MIPS的KernelSpace中,还有两段特殊的地址空间,分别是从0x80000000到0x9FFFFFFF的KernelSpaceUnmappedUncached和0xA0000000到0xBFFFFFFF的KernelSpaceUnmappedCached。
之所以说它们特殊,是因为这两段逻辑地址到物理地址的映射关系是硬件直接确定的,不通过MMU,而且两段实际上是重叠的,均对应0x00000000到0x20000000的物理地址。
那么,为什么一段同样的物理地址有两个逻辑地址对应呢?
它们的区别又在哪里呢?
原来,这是MIPS的设计特色之一。
软件在访问KernelSpaceUnmappedUncached这段地址空间的时候,不经过MIPS的Cache。
这样,虽然速度会比较慢,但是,对于硬件I/O寄存器来说,就不存在所谓的Cache一致性问题。
Cache一致性问题,是指硬件将某个地址的内容跳过软件而改变了,Cache中的内容尚未同步。
这样,如果软件读取该地址,有可能从Cache中获取到错误的内容。
将硬件I/O寄存器设定在这段地址空间,就可以避免Cache一致性带来的问题。
MIPS的程序上电启动地址0xBFC00000,也落在这段地址空间内。
——上电时,MMU和Cache均未初始化,因此,只有这段地址空间可以正常读取并处理。
另一段特殊的地址KernelSpaceUnappedCached,与前者类似,直接映射到0x00000000到0x20000000,与KernelSpaceUnmappedUncached重叠。
因为通过Cache,这段地址空间的访问速度比前者为快。
一般地,这段内存空间用于内核代码段,或者内核中的堆栈。
显然地,当工程师们换算KernelSpace中的这两段的物理地址和逻辑地址时,只需要改变地址的高3bit就可以了。
那么,什么时候需要使用物理地址,什么时候需要使用逻辑地址呢?
我们知道,逻辑地址是程序中访问的内存地址,譬如,下面的这条指令:
lwa0,128(t2)
这条指令的内容是从t2寄存器内的地址+偏移128字节处,读取一个word(4Byte)到寄存器a0内。
如果t2的值为0x88200100,则最终访问的物理地址为0x88200180。
而物理地址,从工程上可以理解为,将逻辑分析仪连接到内存总线(MemoryBus)上,逻辑分析仪指示的地址,就是物理地址。
假如,在上一个例子中,我们把逻辑分析仪接到处理器的前端内存总线,我们就可以看到,执行该指令时,系统访问的物理地址为0x08200180。
物理地址和逻辑地址的换算,不仅限于电子工程师在设计硬件线路时需要。
在内核工程师编写支持DMA的外部设备驱动时,需要将向操作系统申请到的数据缓冲区地址(当然,这是一个逻辑地址)转换为物理地址,并“告诉”相关外设。
这样,外设就可以在收到数据后,使用DMA模式储存在系统的主存中,并向系统发起一个IRQ。
操作系统在IRQ的handler中,从外设的相应IO寄存器读取到这段内存的地址(当然,是物理地址)并转换为逻辑地址并处理之。
这个过程中,如果没有正确使用和分辨物理地址和逻辑地址,驱动程序便会导致内核的一个panic错误。
物理地址到逻辑地址的映射关系是由什么决定的呢?
除了上面提到的两段Unmapped的地址空间,其余都是由TLB确定的,由MMU来执行。
这是后话。
MIPS的协处理器CP0
在MIPS体系结构中,最多支持4个协处理器(Co-Processor)。
其中,协处理器CP0是体系结构中必须实现的。
它起到控制CPU的作用。
MMU、异常处理、乘除法等功能,都依赖于协处理器CP0来实现。
它是MIPS的精髓之一,也是打开MIPS特权级模式的大门。
MIPS的CP0包含32个寄存器。
关于它们的资料可以参照MIPS官方的资料MIPS32(R)ArchitectureForProgrammersVolumeIII:
TheMIPS32(R)PrivilegedResourceArchitecture的Chap7和Chap8。
本文中,仅讨论常见的一些寄存器。
Register0:
Index,作为MMU的索引用。
将来讨论MMU和TLB时会详解之。
Register2,EntryLo0,访问TLBEntry偶数页中的地址低32Bit用。
同上,在MMU和TLB的相关章节中详解。
Register3,EntryLo1,访问TLBEntry奇数页中的地址低32Bit用。
Register4,Context,用以加速TLBMiss异常的处理。
Register5,PageMask,用以在MMU中分配可变大小的内存页。
Register8,BadVAddr,在系统捕获到TLBMiss或AddressError这两种Exception时,发生错误的虚拟地址会储存在该寄存器中。
对于引发Exception的Bug的定位来说,这个寄存器非常重要。
Register9,Count,这个寄存器是R4000以后的MIPS系统引入的。
它是一个计数器,计数频率是系统主频的1/2。
BCM1125/1250,RMIXLR系列以及Octeon的Cavium处理器均支持该寄存器。
对于操作系统来说,可以通过读取该寄存器的值来获取tick的时基。
在系统性能测试中,利用该寄存器也可以实现打点计数。
Register10,EntryHi,这个寄存器同EntryLo0/1一样,用于MMU中。
以后会详述。
Register11,Compare,配合Count使用。
当Compare和Count的值相等的时候,会触发一个硬件中断(HardwareInterrupt),并且总是使用Cause寄存器的IP7位。
Register12,Status,用于处理器状态的控制。
Register13,Cause,这个寄存器体现了处理器异常发生的原因。
Register14,EPC,这个寄存器存放异常发生时,系统正在执行的指令的地址。
Register15,PRID,这个寄存器是只读的,标识处理器的版本信息。
向其中写入无意义。
Register18/19,WatchLo/WatchHi,这对寄存器用于设置硬件数据断点(HardwareDataBreakpoint)。
该断点一旦设定,当CPU存取这个地址时,系统就会发生一个异常。
这个功能广泛应用于调试定位内存写坏的错误。
Register28/29,TagLo和TagHi,用于高速缓存(Cache)管理。
下面,我们来详解CP0中常用的几个寄存器,它们是:
BadVAddr,Count/Compare,Status/Cause,EPC,WatchLo/WatchHi。
BadVAddr:
错误的虚拟地址。
实际上,这个寄存器仅限于出现TLBMiss和ADE(AddressError)两种异常的时候,才能用到。
发生错误的虚拟地址会放在这个寄存器里。
一般地,在设定TLB时,通常将0地址附近的一块,设定为无映射区域。
这样,一旦编程时不慎访问了空指针(0地址),或是空指针加上一定的偏移量,那么,系统就会抛出一个TLBMissException。
在这种情况下,发生错误的地址会被记录在BadVAddr寄存器中。
一般地,这个地址是一个非常接近于0的地址。
往往地,通过BadVAddr在寄存器中的值,和相关数据结构的分析,就可以找出对应的语句。
另外,对于ADE异常,异常地址也会被保存在BadVAddr中。
一般地,操作系统会自行接管这个地址,分两次读取/写入这个地址处的数据,而不会发生CoreDump的情况。
但是,如果这个地址既属于非对齐地址,又属于TLBMiss,那么,系统还是会抛出一个CoreDump的。
正常地,操作系统应当正确处理这个异常,如果在ExceptionHandler中发现地址在TLB中未映射,还是应当抛出ADE异常,而不是TLBMiss。
Count/Compare:
这两个寄存器是CP0中的一对欢喜冤家。
Count是一个计数器,每两个系统时钟周期,Count会增加1,而当它的值和Compare相等时,会发生一个硬件中断(HardwareInterrupt)。
这个特性经常用来为操作系统提供一个可靠的tick时脉。
Status:
这个寄存器标识了处理器的状态。
其中,中断控制的8个IM(InterruptMask)位和设定处理器大小端的RE(ReverseEndianess)位。
8个IM位,分别可以控制8个硬件中断源。
它们将在讲述‘硬件中断’时详解。
RE位很有趣,设定这个Bit可以让CPU在大端(BigEndian)和小端(LittleEndian)之间切换。
默认情况下,MIPS处理器是大端的,和网络序相同。
但是,为了能在MIPS上运行类似WindowsNT的服务器操作系统,设定这个Bit可以令处理器工作在LittleEndian模式下。
Cause:
在处理器异常发生时,这个寄存器标识出了异常的原因。
其中,最重要的是从Bit2到Bit6,5个Bit的ExcetionCode位。
它们标识出了引起异常的原因。
具体数值代表的异常类型,如下所示:
0:
Interrupt,中断;
1:
TLBModified,试图修改TLB中映射为只读的内存地址;
2:
TLBMissLoad,试图读取一个没有在TLB中映射到物理地址的虚拟地址;
3:
TLBMissStore,试图向一个没有在TLB中映射到物理地址的虚拟地址存入数据;
4:
AddressErrorLoad,试图从一个非对齐的地址读取信息;
5:
AddressErrorStore,试图向一个非对齐的地址写入信息;
6:
InstructionBusError,一般是指令Cache出错;
7:
DataBusError,一般是数据Cache出错;
8:
Syscall,由syscall指令产生。
操作系统下,通用的由用户态进入内核态的方法。
可以类比IA32的“调用门”理解;
9:
BreakPoint,由break指令产生。
最常见的bp指令,是由编译器产生的,在除法运算时插入一个breakpoint指令,以达到在除0时抛出错误信息的目的。
因此,如果在定位问题时发现了一个BreakPoint异常,且它的异常分代码为07,应当考虑是出现了除0的情形;
10:
RI,保留指令。
在CPU执行到一条没有定义的指令时,进入此异常;
11:
Co-processorUnavilible,协处理器不可用。
这个异常是由于试图对不存在的协处理器进行操作引起的。
特别的,在没有浮点协处理器的处理器上执行这条命令,会导致这个异常。
随之,操作系统会调用模拟浮点的lib库,来实现软件的浮点运算;
12:
Overflow,算术溢出。
只有带符号的运算会引起这个异常;
13:
Trap,这个异常来源于trap指令。
和syscall指令类似地,trap指令也会引起一个异常,但trap指令可以附带一些条件,这样可以用于调试程序用。
14:
VCEI,指令高速缓存中的虚地址一致性错误。
(没明白怎么回事,还有待高手补充)
15:
FloatPointException,浮点异常;
16:
Co-processor2Exception,协处理器2的异常;
17~22,留作将来的扩展;
23:
Watch,内存断点异常。
当设定了WatchLo/WatchHi两个寄存器时起作用。
当load/store的虚拟地址和WatchLo/WatchHi中匹配时,会引发这样一个异常;/*这个地方经典著作《SeeMIPSRun》犯了一个错误,将虚拟地址写成了物理地址*/
24~30,留作将来的扩展;
EPC:
这个寄存器的作用很简单,就是保存异常发生时的指令地址。
从这个地方可以找到异常发生的指令,再结合BadVAddr,sp,ra等寄存器,就可以推导出异常时的程序调用关系,从而定位问题的根因。
一旦异常发生时EPC的内容丢失,那么对异常的定位将是一件非常困难的事情。
WatchLo/WatchHi:
这一对寄存器可以用来设定“内存硬件断点”,也就是对指定点的内存进行监测。
当访问的内存地址和这两个寄存器中地址一致时,会发生一个异常。
为了适应64Bit的一些扩展功能,某些MIPS处理器又对这两个寄存器的功能做了一些修改,与MIPS体系结构的定义已经有了差别,如RMI的多核处理器等。
对于协处理器CP0的访问,需要使用特别的指令。
这些指令属于“特权级指令”,只有在内核态(KernelMode)下才能执行。
如果在用户态下,会引起一个异常(Exception)。
对CP0的主要操作有以下的指令:
mfc0rt,rd将CP0中的rd寄存器内容传输到rt通用寄存器;
mtc0rt,rd将rt通用寄存器中内容传输到CP0中寄存器rd;
mfhi/mflort将CP0的hi/lo寄存器内容传输到rt通用寄存器中;
mthi/mtlort将rt通用寄存器内容传输到CP0的hi/lo寄存器中;
当MIPS体系结构演进到MIPSIV的64位架构后,新增了两条指令dmfc0和dmtc0,向CP0的寄存器中读/写一个64bit的数据。
前面提到,MIPS体系结构是一个无互锁,高度流水的五级pipeline架构,这就意味着,前一条指令如果尚未执行完,后一条指令有可能已经进入了取指令/译码阶段。
这样,就有可能发生所谓的CP0冒险(CP0Hazard)现象。
简单地说,就是mfc和mtc指令的执行速度是比较慢的,因此,开始执行完下一条指令时,有可能CP0寄存器的值尚未最后传输到指定的目标通用寄存器中。
此时,如果读取该通用寄存器,有可能并未得到正确的值。
这就是所谓的CP0冒险现象。
为了避免CP0冒险,我们在编程时需要在CP0操作指令的后面加上一条与前一条指令的目的通用寄存器无关的指令,也就是所谓的延迟槽(delayslot)。
如果对性能不敏感,可以考虑用一条nop空操作指令填充延迟槽。
例如,经常在异常处理例程(exceptionhandler)中出现的内容:
....
mfc0k0,$cause
nop/*mfc0指令执行速度慢,在延迟槽中加一个空操作*/
movt0,k0/*将cause寄存器内容放到t0,进行下一步操作*/
对于异常处理的具体内容,大家可以在下一章《MIPS的异常与中断》中看到。
MIPS的异常和中断,同其他体系结构,例如Intel的IA32架构下的中断/调用门/陷阱机制类似,其目的主要有三:
一,提供一个合法地从用户态到内核态的切换通道,使得程序能够访问如CP0、KSeg等平时被保护的资源;
二,处理一些非法的操作,如TLBMiss/AddressE
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 信息时代 世界 有着 光鲜 亮丽 外壳
![提示](https://static.bdocx.com/images/bang_tan.gif)