处理器工作模式 |
特权模式 |
异常模式 |
说明 |
用户模式(User, usr) |
--- |
--- |
正常程序执行的模式 |
系统模式(System, sys) |
特权模式 可访问任意系统资源 |
--- |
用于运行特权级的操作系统任务 |
管理模式(Supervisor, sve) |
异常模式 通常由系统异常状态切换进来 |
供操作系统使用的一种保护模式 |
|
外部中断模式(IRQ, irq) |
用户通常的中断使用 |
||
快速中断模式(FIQ, fiq) |
用于高速数据传输和通道处理 |
||
数据访问中止模式(Abort, abt) |
用于虚拟存储及存储保护 |
||
未定义指令中止模式(Undefin- ed, und) |
用于支持通过软件仿真硬件的协处理器 |
(1)用户模式:用户程序的工作模式,运行在操作系统的用户态,没有权限操作其它硬件资源,只能执行处理自己的数据,也不能直接切换到其它模式,要想访问硬件资源或切换到其它模式,只能通过软中断或产生异常。在Linux操作系统中,用户空间进程就是在这种模式下运行;
(2) 系统模式:特权模式,和用户模式共用一套寄存器(包括R15-PC,程序计数器),该模式下可访问用户模式的寄存器,且操作系统的一些特权任务可用此模式访问受控的资源,系统模式不能由任何异常进入;
---------- ARM进入以下几种模式后,都要给R15-PC寄存器重新赋值,开始执行新的指令 ----------
(3)管理模式:操作系统使用的保护模式,该模式主要用于系统的初始化和软中断处理。当ARM刚上电或复位时,进入该模式,并强制PC从0x0000 0000处取指令;当系统软中断(如系统调用,调用ARM的SWI汇编指令)时,进入该模式,并强制PC从0x0000 0008处取指令 —— 这也是ARM从用户模式主动切入管理模式(Linux从用户态主动进入内核态)的唯一方法;在Linux操作系统中,内核空间在这种模式下运行;
---------- 当出现异常状况时,Linux还有可能在如下几种ARM模式下运行,如硬件中断、程序问题导致的异常等 ----------
(4) 外部中断模式:特权模式和异常处理模式,用于处理一般的中断请求,通常在硬件产生中断信号之后自动进入该模式,可访问任意硬件资源。 进入该模式后、ARM强制PC从0x0000 0018处取指令;
(5) 快速中断模式:快速中断模式是相对一般中断模式而言的,它是用来处理对时间要求比较紧急的中断请求,主要用于高速数据传输及通道处理中。进入该模式后,ARM强制PC从0x0000 001C处取指令;
(6) 数据访问中止模式:用于支持虚拟内存或存储器保护,当用户程序访问非法地址,没有权限读取的内存地址时,会进入该模式,linux下编程时经常出现的segment fault通常都是在该模式下抛出返回的。当指令取终止时,进入该模式,并强制PC从0x0000 000C处取指令;当数据取终止时,进入该模式,并强制PC从0x0000 0010处取指令。
(7) 未定义指令中止模式:用于支持硬件协处理器的软件仿真,CPU在指令的译码阶段不能识别该指令操作时,会进入未定义模式。当程序中出现未定义的指令时,进入该模式,并强制PC从0x0000 0004处取指令。
首先,ARM开发板在刚上电或复位后都会首先进入SVC即管理模式,此时,程序计数器R15-PC值会被赋为0x0000 0000。bootloader就是在此模式下,位于0x0000 0000的NOR FLASH或SRAM中装载的。因此,开机或重启后bootloader会被首先执行。
接着,bootloader引导Linux内核,此时,Linux内核一样运行在ARM的SVC即管理模式下。当内核启动完毕,准备进入用户态init进程时,内核将ARM的当前程序状态寄存器CPSR的M[4:0]设置为0x10000,进而用户态程序运行在ARM的用户模式。由于用户模式下对资源的访问受限,因此可以达到保护Linux操作系统内核的目的。
ARM有37个32-Bits长的寄存器,各工作模式下的通用寄存器和状态寄存器如下表。
> 通用寄存器:30个;
> 程序计数器 (PC):1个;
> 状态寄存器:6个,1个当前程序状态寄存器 (current program status register,CPSR),5个备份程序状态寄存器 (saved program status register,SPSR);
R0~R7:未分组的寄存器,对于任何处理器模式,这些寄存器都对应于相同的32位物理寄存器;
R8~R14:分组寄存器,它们所对应的物理寄存器,取决于当前的处理器模式,几乎所有允许使用通用寄存器的指令都允许使用分组寄存器;
R13:堆栈指针(SP),在ARM指令集当中,没有以特殊方式使用R13的指令或其它功能,只是习惯上都这样使用,但是在Thumb指令集中存在使用R13的指令;
R14:链接寄存器(LR),在结构上有两个特殊功能:
a. 在每种模式下,模式自身的R14版本用于保存子程序返回地址;
b. 当发生异常时,将异常模式的对应R14设置为异常返回地址(有些异常有一个小的固定偏移量)。
aa
Linux中断时进入中断处理例程执行程序时,处理器所处的模式还是SVC模式,即管理模式。实际上,在中断到来时,程序上做了处理,以使得进入SVC模式而不是中断模式。如下面Linux内核汇编代码linux/arch/arm/kernel/entry-armv.S中的代码片段,确实内核代码使得中断时进入的还是SVC模式。
/*
* Vector stubs.
*
* This code is copied to 0xffff1000 so we can use branches in the
* vectors, rather than ldr's. Note that this code must not exceed
* a page size.
*
* Common stub entry macro:
* Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*
* SP points to a minimal amount of processor-private memory, the address
* of which is copied into r0 for the mode specific abort handler.
*/
.macro vector_stub, name, mode, correction=0
.align 5
vector_\name:
.if \correction
sub lr, lr, #\correction
.endif
@
@ Save r0, lr_ (parent PC) and spsr_
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr // 以上3句是将spsr中的模式位改为svc,注意这里只是修改了irq的spsr,还未切换状态
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f // 取出spsr中的[M3:M0],因M4表示26位还是32位寻址,如果只想要模式,取[M3:M0]就行
THUMB( adr r0, 1f )
THUMB( ldr lr, [r0, lr, lsl #2] )
mov r0, sp // 将irq模式的栈基地址填到r0,以后在svc_entry中可以通过r0去读取irq的栈
ARM( ldr lr, [pc, lr, lsl #2] ) // 将[M3:M0]乘4再加pc,计算出入口(一个entry占4字节),结果usr还是svc要看中断之前的模式
movs pc, lr @ branch to handler in SVC mode //跳转入口(__irq_usr或__irq_svc),mov带了s,用spsr去填cpsr
ENDPROC(vector_\name)
.align 2
@ handler addresses follow this label
1:
.endm
.section .stubs, "ax", %progbits
__stubs_start:
@ This must be the first word
.word vector_swi
vector_rst:
ARM( swi SYS_ERROR0 )
THUMB( svc #0 )
THUMB( nop )
b vector_und
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4 // 宏vector_\name展开,展开后,上述ARM(ldr lr,[pc,lr,lsl #2])处PC值为下面__irq_usr的地址
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
// 其中一些是占位符,这里刚好16个,对应了[M3:M0]的所有可能取值,所以entry的地址就可以用[M3:M0]很快的算出来,之所以弄16个entry,是因为[M3:M0]在各种模式的取值中并不是连续的