arm linux之data abort异常处理
data abort是ARM体系定义的异常之一。异常发生时,ARM会自动跳转到异常向量表中,通过向量表中的跳转命令跳转到相应的异常处理中去
svc模式进入data abort
svc模式进入data abort,也就是Linux的内核模式进入data aboart时,会跳转到__dabt_svc。
__dabt_svc:
svc_entry @ 保护寄存器现场
mrs r9, cpsr
tst r3, #PSR_I_BIT @ 检查是否要开中断
biceq r9, r9, #PSR_I_BIT
bl CPU_DABORT_HANDLER @ 处理异常之前的准备工作
msr cpsr_c, r9
mov r2, sp
bl do_DataAbort @ 就是这里跳转到内核C函数
disable_irq
ldr r0, [sp, #S_PSR]
msr spsr_cxsf, r0
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
ENDPROC(__dabt_svc)
/*
* Dispatch a data abort to the relevant handler.
*/
asmlinkage void __exception
do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
{
const struct fsr_info *inf = fsr_info + fsr_fs(fsr);
struct siginfo info;
if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs))
return;
printk(KERN_ALERT "Unhandled fault: %s (0x%03x) at 0x%08lx\n",
inf->name, fsr, addr);
info.si_signo = inf->sig;
info.si_errno = 0;
info.si_code = inf->code;
info.si_addr = (void __user *)addr;
arm_notify_die("", regs, &info, fsr, 0);
}
static struct fsr_info fsr_info[] = {
/*
* The following are the standard ARMv3 and ARMv4 aborts. ARMv5
* defines these to be "precise" aborts.
*/
{ do_bad, SIGSEGV, 0, "vector exception" },
{ do_bad, SIGBUS, BUS_ADRALN,"alignment exception" },
{ do_bad, SIGKILL, 0, "terminal exception" },
{ do_bad, SIGBUS, BUS_ADRALN,"alignment exception" },
{ do_bad, SIGBUS, 0, "external abort on linefetch" },
{ do_translation_fault,SIGSEGV, SEGV_MAPERR, "section translation fault" },
{ do_bad, SIGBUS, 0, "external abort on linefetch" },
{ do_page_fault,SIGSEGV, SEGV_MAPERR, "page translation fault" },
{ do_bad, SIGBUS, 0, "external abort on non-linefetch" },
{ do_bad, SIGSEGV, SEGV_ACCERR, "section domain fault" },
{ do_bad, SIGBUS, 0, "external abort on non-linefetch" },
{ do_bad, SIGSEGV, SEGV_ACCERR, "page domain fault" },
{ do_bad, SIGBUS, 0, "external abort on translation" },
{ do_sect_fault,SIGSEGV, SEGV_ACCERR, "section permission fault" },
{ do_bad, SIGBUS, 0, "external abort on translation" },
{ do_page_fault,SIGSEGV, SEGV_ACCERR, "page permission fault" },
/*
* The following are "imprecise" aborts, which are signalled by bit
* 10 of the FSR, and may not be recoverable. These are only
* supported if the CPU abort handler supports bit 10.
*/
{ do_bad, SIGBUS, 0, "unknown 16" },
{ do_bad, SIGBUS, 0, "unknown 17" },
{ do_bad, SIGBUS, 0, "unknown 18" },
{ do_bad, SIGBUS, 0, "unknown 19" },
{ do_bad, SIGBUS, 0, "lock abort" }, /* xscale */
{ do_bad, SIGBUS, 0, "unknown 21" },
{ do_bad, SIGBUS, BUS_OBJERR, "imprecise external abort" }, /* xscale */
{ do_bad, SIGBUS, 0, "unknown 23" },
{ do_bad, SIGBUS, 0, "dcache parity error" }, /* xscale */
{ do_bad, SIGBUS, 0, "unknown 25" },
{ do_bad, SIGBUS, 0, "unknown 26" },
{ do_bad, SIGBUS, 0, "unknown 27" },
{ do_bad, SIGBUS, 0, "unknown 28" },
{ do_bad, SIGBUS, 0, "unknown 29" },
{ do_bad, SIGBUS, 0, "unknown 30" },
{ do_bad, SIGBUS, 0, "unknown 31" },
};
fsr_info对大多数abort都调用do_bad函数处理,do_bad函数简单返回1,这样就可以继续执行上面提到的arm_notify_die。
fsr_info对以下四种特殊abort将作单独处理:
l "section translation fault" do_translation_fault
段转换错误,即找不到二级页表
l "page translation fault" do_page_fault
页表错误,即线性地址无效,没有对应的物理地址
l "section permission fault" do_sect_fault
段权限错误,即二级页表权限错误
l "page permission fault" do_page_fault
页权限错误
段权限错误 do_sect_fault
do_sect_fault函数直接调用do_bad_area作处理,并返回0,所以不会再经过arm_notify_die。do_bad_area中,判断是否属于用户模式。如果是用户模式,调用__do_user_fault函数;否则调用__do_kernel_fault函数。
void do_bad_area(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
if (user_mode(regs))
__do_user_fault(tsk, addr, fsr, SIGSEGV, SEGV_MAPERR, regs);
else
__do_kernel_fault(mm, addr, fsr, regs);
__do_user_fault中,会发送信号给当前线程。
__do_kernel_fault则比较复杂:
l 调用fixup_exception进行修复操作,fixup的具体细节可在内核文档exception.txt中找到,它可用于处理get_user之类函数传入的地址参数无效的情况。
l 如果不能修复,调用die函数处理oops。
l 如果没有进程上下文,内核会在上一步的oops中panic。所以到这里肯定有一个进程与之关联,于是调用do_exit(SIGKILL)函数退出进程,SIGKILL会被设置在task_struct的exit_code域。
段表错误 do_translation_fault
do_translation_fault函数中,会首先判断引起abort的地址是否处于用户空间。
l 如果是用户空间地址,调用do_page_fault,转入和页表错误、页权限错误同样的处理流程。
l 如果是内核空间地址,会判断该地址对应的二级页表指针是否在init_mm中。如果在init_mm里面,那么复制该二级页表指针到当前进程的一级页表;否则,调用do_bad_area处理(可能会调用到fixup)。
对段表错误的处理逻辑的个人理解如下(不保证完全准确):
Linux产生段表错误,除了fixup之外还有两种原因:一个是用户空间映射的线性地址出现异常,另一个是内核中调用vmalloc分配的线性地址出现异常。对用户空间地址的异常处理很容易理解。对于内核地址,从vmalloc的实现代码中可以看到,它分配的线性空间的映射关系都会保存到全局变量init_mm中,所以,任何vmalloc生成的线性空间的二级页表都应该在init_mm中找到。(init_mm是内核的mm_struct,管理整个内核的内存映射)。
从这里也可以看出,对vmalloc的地址访问可能会产生两次异常:第一次是段表错误,生成二级页表;第二次是页表错误,分配真正的物理页面到线性空间。
关于init_mm的细节可以参考http://my.chinaunix.net/space.php?uid=25471613&do=blog&id=323374,大概意思是内核页表改变时只改变init进程的内核页表init_mm,其它进程通过缺页异常从init_mm更新自己维护的内核页表。
页表错误 do_page_fault
页权限错误 do_page_fault
do_page_fault完成了真正的物理页面分配工作,另外栈扩展、mmap的支持等也都在这里。对于物理页面的分配,会调用到do_anonymous_page->。。。-> __rmqueue,__rmqueue中实现了物理页面分配的伙伴算法。
如果当前没有足够物理页面供内存分配,即分配失败:
l 内核模式下的abort会调用__do_kernel_fault,这与段权限错误中的处理一样。
l 用户模式下,会调用do_group_exit退出该任务所属的进程。
用户程序申请内存空间时,如果库函数本身的内存池不能满足分配,会调用brk系统调用向系统申请扩大堆空间。但此时扩大的只是线性空间,直到真正使用到那块线性空间时,系统才会通过data abort分配物理页面。用户空间的malloc函数返回不为NULL只能说明得到了线性空间的资源,但物理内存可能并没有映射上去,所以真正物理内存分配失败时,进程还是会以资源不足为由,直接退出。
比如段错误:
do_translation_fault
do_bad_area
__do_kernel_fault
die("Oops", regs, fsr);
这里 fsr_info分类的几种错误类型,详细介绍请参考博文:
http://blog.csdn.net/walkingman321/article/details/6230334
http://blog.csdn.net/walkingman321/article/details/6238608
更多推荐
所有评论(0)