Linux Kernel Mode与User
Mode的实质
---Linux
在ARM Architecture上的实现
Linux
Kernel作为一种操作系统有别与一般的用户程序,即所谓的kernel mode和user mode。Kernel
mode下运行的程序能够访问所有的memory,能够处理interrupt;而user
mode的程序只能访问有限的memory,不能直接处理中断。那么kernel mode和user
mode的这些差别是由谁来决定的呢?归根结底,所有的程序都是CPU的指令,同样的指令有的时候能做某件事,有的时候不能,这都是由指令执行时CPU的Mode决定的。简而言之就是说,执行Kernel指令的时候,CPU处于特权模式,可以访问所有Memory;执行user
program的指令时,CPU处于user
mode,读写memory受限制;而当Interrupt发生时,因为要操作硬件,所以CPU会从原来工作的Mode切换到特权模式来执行Interrupt
handler。下面将详细阐述Linux操作系统是怎样在ARM architecture上运转的。
ARM的Processor
Mode
ARM有以下7种Processor
Mode,当前处于的模式有程序状态寄存器Current Program Status Register
(CPSR)的bit4:0决定:
User: 用户模式User Mode
FIQ : 快速中断Fast
Interrupt Request
IRQ: 普通中断Normal
Interrupt Request
SVC: 管理模式Supervisor
Mode
Abort: 异常模式 Abort
Mode
Undefined: 未定义模式 Undefined
Mode
System: 系统模式
其中User
Mode是一种受限制的模式,其他6种都是特权模式(Privilege Mode)。
这些模式间可以相互切换,切换方法可以归结为以下两种:
第一种:由CPU本身硬件完成切换
CPU本身主动进行模式切换是因为在执行指令的时候发生了某种“意外“,这种”意外“就是Exception。一旦Exception发生,CPU就要立即切换到这种Exception相对应的模式去处理该Exception。所有Exception都在Privilege
Mode下处理。ARM CPU共有6种Exception,他们发生的条件和发生后进入的CPU模式如下:
Exception
触发条件
进入CPU模式
Reset
CPU的Reset pin被Assert (CPU Power
on时,Reset pin会被Assert)
SVC
Undefined Instruction:
企图执行一条不认识的指令
Undefined
Software Interrupt (SWI)
执行指令SWI
SVC
Prefetch Abort
获取指令失败
Abort
Data Abort
获取数据失败
Abort
IRQ
普通中断发生,CPU的IRQ pin被Assert
IRQ
FIQ
快速中断发生,CPU的FIQ pin被Assert
FIQ
请注意Exception和CPU模式中都有IRQ和FIQ,但他们的意义是不同的。前者指一个Exception的事件,后者指一种CPU运行的模式。
第二种:程序修改CPSR
某些指令是可以修改CPSR的,在程序中执行这些指令就能切换CPU的模式。但是这些模式两两之间并不是都可以通过修改CPSR来实现切换的,如User
Mode下不可以修改CPSR切换到其他Mode。通常从Exception中返回时要修改CPSR,从而恢复到Exception发生时的CPU
Mode继续执行。
Linux的Kernel
mode和User mode
Linux
Kernel工作在Privilege
mode下,它需要处理所有在privilege
mode下完成的工作。如IRQ,FIQ
Exception发生时,进入相应的Privilege
Mode,由Kernel来处理相应的IRQ, FIQ事件。也就是说,Kernel对应CPU的6中Privilege
Mode。正是因为privilege
mode可以对系统资源“特权“的访问,所以kernel能够控制所有的软硬件资源,因而被称为”操作系统“。
User mode运行在CPU的User processor
mode下,所以在访问memory等方面都有限制,用户的各种Application都运行在User Mode下。User Mode下的程序想要访问只有Privilege
Mode才能访问的资源就必须使用系统调用(System Call),进入Kernel Mode来处理。从User Mode进入Priviledge
mode的唯一方式是swi指令,这条指令执行会触发Software Interrupt
Exception, 从而进入SVC模式。所以System call的实质就是通过swi指令进入Kernel Mode。在kernel中处理完毕后,kernel会将CPU切回到User Mode继续执行,同时将调用结果返回到User Mode。
下表说明了CPU Mode与Linux
Mode之间的对应关系:
User
FIQ
IRQ
Supervisor
Abort
Undefined
System
Kernel Mode
√
√
√
√
√
√
User Mode
√
CPU
Mode切换的场合在Linux中的实现
如 前所述,所有CPU
privilege模式下的工作都对应于OS的Kernel mode,那么所谓Linux Kernel mode和User
mode之间的切换映射到CPU层面来说就是CPU的User mode怎样切换到其他6种mode,每种privilege
mode又怎样切换到User mode的。同时,6中Privilege
Mode之间当Exception发生时也会切换。下面我们将对照Linux kernel
2.6.33的代码来逐一分析每种Exception发生时Linux Kernel是怎样处理的。
在ARM
Architecture中,Exception发生时指令将从固定的地址开始执行。所以必须在这些地址上放置正确的代码来处理相应的Exception。这些固定的地址也称向量,由CP15
Reg1中的V
bit控制,可以在虚拟内存的底部或顶部。注意是虚拟内存,也就是说这些向量的固定地址是虚拟地址,是要经过MMU转换的。各Exception对应的向量地址(虚拟地址)如下:
Low Vector
High Vector
Reset
0
0xFFFF0000
Undefined Instruction:
0x04
0xFFFF0004
Software Interrupt (SWI)
0x08
0xFFFF0008
Prefetch Abort
0x0C
0xFFFF000C
Data Abort
0x10
0xFFFF0010
IRQ
0x18
0xFFFF0018
FIQ
0x1C
0xFFFF001C
在Linux
kernel中向量位于高地址,由CONFIG_VECTORS_BASE控制,Default为0xFFFF0000,在__arm926_setup中将CP15
control register 1的V bit置为1(High
Vector)。向量表在源代码里位于entry-armv.S中,从__vector_start开始,在FIQ,
IRQ等被enable前,必须要copy到0xFFFF0000开始的高地址。Copy的时点在start_kernel
()->setup_arch()->early_trap_init()中。在setup_arch()之后的local_irq_enable()才是真正clear
CPSR的I-bit,至此,IRQ才Enable了。
下面逐一讲述各Exception
Handler的实现。
Reset
CPU Power on后即触发Reset
Exception。Reset发生后,CPU进入SVC Mode,
PC被设置为0,即从内存地址0开始执行代码。这是系统启动的开端。对应arm
Linux中,arch/arm/kernel/head.S中的“__HEAD"开始即是系统执行的Kernel的第一行代码(2.6.33中是设置CPSR,disable
FIQ, IRQ)。用objdump
vmlinux也可以看到此行代码是Kernel的第一条指令。详细Linux启动的过程在”Linux
Kernel的启动过程“一文中阐述。
在向量表中Reset
Exception的向量处是执行swi SYS_ERROR0,这条指令将触发SWI
Exception,在vector_swi中通过判断参数,最终将执行到die(),系统停止运行。也就是说,Linux在运行过程中发生Reset
Exception,将被认为是系统错误而停止。
发生IRQ
IRQ
Exception发生时将从__vector_irq开始执行。这部分代码稍显晦涩。源代码中__vector_irq这个符号是由__vector_stub这个宏产生的。__vector_stub这个宏实现了IRQ和Abort
Exception的共用部分,主要是保存SPSR,然后根据发生Exception时 的Program
Mode跳转到相应的处理分支上。处理分支的table是紧跟在_vector_stub之后,可以看到每一种Program
mode下发生IRQ对应于table中的一个entry。SVC下发生IRQ的时候就进入__irq_svc,User
Mode下发生IRQ的时候就进入__irq_user。关于__irq_usr和__irq_svc中具体是怎样处理IRQ的,详见“Linux中IRQ的处理“一文。
发生Prefetch
Abort
与IRQ一样,发生Prefrech
Abort时将跳转到__pabt_usr和_pabt_svc的分支上处理。
发生Data Abort
同样,发生Data
Abort时将跳转到__dabt_usr和_dabt_svc的分支上处理。
发生Undefined Instruction
Abort
同样,发生Undefined
InstructionAbort时将跳转到__und_usr和_und_svc的分支上处理。
__irq_usr,__pabt_usr, __dabt_usr,
__und_usr都是从User Mode发生Exception进入到相应 Mode,完成相应处理后在返回User Mode,所以在入口处有宏user_entry来处理User
Mode->相应exception Mode的模式切换,__irq_usr处理完IRQ之后需要返回User
Mode,宏ret_to_user来完成各exception Mode->User Mode的切换。
__irq_svc,__pabt_svc, __dabt_svc,
__und_usr都是从SVC Mode发生Exception进入到相应 Mode,完成相应处理后在返回SVC Mode,所以在入口处有宏svc_entry来处理SVC
Mode->P相应exception Mode的模式切换,exception handler处理完之后需要返回SVC Mode,宏svc_exit来完成各exception Mode->User Mode的切换。
发生SWI
SWI是由swi指令触发的exception,目的是从user mode进入SVC
mode。可想而知所有User模式下软件的系统调用(system call)都是通过swi指令进入Kernel模式的。System
call的实质就是swi指令。Glibc中包含了一系列System call,如最常用open, read, write,
ioctl,每个system call对应一个system call number。每个system
call在kernel中都有相对应函数来完成。在__vector_start开始的向量表中SWI exception
handle将从vector_swi开始执行。实际vector_swi的代码在entry-common.S中。vector_swi主要做得事情就是根据User
Mode执行swi时存在寄存器中的system call number找到kernel中对应的handler
routine,并跳转到该routine执行。System call带有参数,具体这些参数怎样存放在寄存器中由Application
Binary
Interface的版本规定。所以在vector_swi中可以看到很多OABI,EABI的#ifdef判断。vector_swi最重要的地方是根据system
call number (scno)跳转的相应的handler routine执行。Handler
routine的table在calls.S中,可以看到handler routine都是以sys_开头的,总共有360多个system
call。sys_read对应于User
Mode下的系统调用函数read,sys_socket对应于socket。执行完handler
routine后,通过ret_fast_syscall返回User Mode。这里还有一点要说明,在User Mode下System call是通过swi指令进入Kernel,在Kernel里同样也可以执行swi指令触发SWI Exception,同样也是从vector_swi开始执行。这种情况下会跳转到arm_syscall,这是Kernel内部用于对arm进行某些操作,如cache flush等。
发生FIQ
向量表上FIQ exception将跳转到vector_fiq,但vector_fiq实际只是简单的返回了。应该可以根据需要在这里添加代码来处理FIQ。