系统调用与软中断关系
1.EABI与OABI方式的系统调用
在linux中系统调用是通过软中断实现,应用层通过int syscall(int number, ...);接口将syscall number 放在提前约定好的位置,然后产生软中断swi,并跳转到中断向量表执行。没有接触过的人可能会有疑问:kernel是和什么“提前约定”的。答案是编译器,应用层的系统调用具体实现是在编译器的链接库里,打开你的编译器所在地址,你会发现编译器的工具包里有lib/ 和 include/文件夹,这里存放的就是kernel正常工作需要的链接库,也就是说在应用层编写的代码发起一个open的syscall,它到底向kernel发出了什么样的命令是由编译器决定的。由于这一块我并没有具体了解过,这里只做一个不精确的描述,不再具体展开。
早期linux采用OABI方式传递系统调用的number。流程如下:
1.将number 放在软终断的中断号内( eg: swi __NR_OABI_SYSCALL_BASE | numbe)。
2.在跳转到软中断后,再在之前已经执行过的指令所在内存地址中获取当时的指令(ldr r10, [lr, #-4]),并解析出syscall number。
很明显读取已经执行过的指令的内存地址处的数据是一种很原始的行为,现在用的不多,但代码依然保留了下来。
现在linux采用新的EABI(Embedded)方式传递系统调用的number。比如现在的android编译器arm-linux-androideabi-gcc。这种新的系统调用方式流程如下:
1.将syscall number 存储到r7寄存器中。
2. 执行swi指令跳转到软中断处执行,并从r7中获取syscall number。
注意这里提到的软中断是 supervisor call exception(svc),由于原来名字是software interrupt(swi)一直沿用软中断的叫法,但与linux kernel的softirq实现是完全不同的两个概念。前者是借助硬件实现的一种,使软件可以进入特权模式的指令,后者是kernel完全以软件实现的短暂延时的轻量级处理(出于软中断上下文)
2.软中断到syscall代码分析
由于需要涉及汇编代码,做一下说明,本文只分析arm架构下的系统调用流程,下面是arm架构的准备知识:
1.应用运行于arm的usr_mode,kernel运行在arm的svc_mode,这两种模式共用r0-r12寄存器以及PC(r15),usr_mode下没有SPSR(r17)寄存器,svc_mode 下有自己的sp(r13),lr(r14),spsr(r17)。
2.从usr_mode发生软中断(swi)跳转到svc_mode时,原来的cpsr会被arm core自动备份到spsr_svc中,以供从svc返回时恢复usr完整的上下文(r0-r12+sp+lr+pc+cpsr)。
3.arm架构下linux使用的是满减栈,栈顶指针sp永远指向栈顶的元素,栈顶向低地址方向生长。
4.无论采取多少级的流水,arm架构下前三级是fetch,decode,execute。PC永远指向fetch地址。
5.lr地址 "可以认为" 是指向跳转前的decode地址。
整个中断的过程,不是本文关注重点,我们只从进入软中断后开始分析。涉及文件:
arch/arm/kernel/entry-armv.S //中断向量表定义位置,本文不分析
arch/arm/kernel/entry-common.S //软中断真正入口地址
arch/arm/kernel/entry-header.S //部分定义
entry-header.S:
scno .req r7 @ syscall number
tbl .req r8 @ syscall table pointer
why .req r8 @ Linux syscall (!= 0)
tsk .req r9 @ current thread_info 进程栈的栈底,存储着该进程的基本信息如id 调度优先级 等等
entry-common.S:
ENTRY(vector_swi)
/****************************将中断发生前的寄存器状态备份入栈**********/
sub sp, sp, #S_FRAME_SIZE /*在栈上开辟18*4的空间备份跳转之前的寄存器值*/
stmia sp, {r0 - r12} /* Calling r0 - r12 备份公用寄存器r0-r12*/
ARM( add r8, sp, #S_PC ) /*r8指向栈空间中应该存pc的位置*/
ARM( stmdb r8, {sp, lr}^ )/* Calling sp, lr*/
/*备份usr_mode的r13 r14 这里^表示被备份的sp lr 是usr_mode的寄存器不是目前所在svc_mode的寄存器*/
mrs r8, spsr // called from non-FIQ mode, so ok.从usr发生软中断跳转到svc时,原来的cpsr会被存储到spsr_svc中
str lr, [sp, #S_PC] // Save calling PC 备份pc
str r8, [sp, #S_PSR] // Save CPSR 备份跳转前的cpsr到栈上
str r0, [sp, #S_OLD_R0] // Save OLD_R0 备份r0到栈空间中r17的位置(因为没有SPSR 所以这里多备份了一个r0,估计是为了标准化)
zero_fp //清空fp寄存器r11
/*********************取得syscall number**************************/
/*
* Get the system call number.
*/
#if defined(CONFIG_OABI_COMPAT)
/*这里是OABI方式的syscall number传递
* If we have CONFIG_OABI_COMPAT then we need to look at the swi
* value to determine if it is an EABI or an old ABI call.
*/
ldr r10, [lr, #-4] // get SWI instruction 利用lr取得swi指令的内容
//跳转前code段的内存{地址:内容:流水级},从下面的内存分布可以清楚看出lr - 4的含义
//{0x8:xxx:fetch<--pc},{0x4:xxx:decode<--lr},{0x0:swi num:exe}
#ifdef CONFIG_