您的位置 首页 新能源

ARM Linux反常处理之data abort

1异常向量与程序跳转dataabort是ARM体系定义的异常之一。异常发生时,ARM会自动跳转到异常向量表中,通过向量表中的跳转命令跳转到相应…

1 反常向量与程序跳转

data abortARM体系界说的反常之一。反常发生时,ARM会主动跳转到反常向量表中,经过向量表中的跳转指令跳转到相应的反常处理中去。
ARM的反常处理向量表在entry-armv.S文件中:

.globl      __vectors_start__vectors_start:swi   SYS_ERROR0b     vector_und + stubs_offsetldr   pc, .LCvswi + stubs_offsetb     vector_pabt + stubs_offsetb     vector_dabt + stubs_offsetb     vector_addrexcptn + stubs_offsetb     vector_irq + stubs_offsetb     vector_fiq + stubs_offset

关于data abort,对应的跳转地址是vector_dabt + stubs_offset。这个地址的指令界说也在entry-armv.S:

vector_stub     dabt, ABT_MODE, 8.long       __dabt_usr                       @  0  (USR_26 / USR_32).long       __dabt_invalid                     @  1  (FIQ_26 / FIQ_32).long       __dabt_invalid                     @  2  (IRQ_26 / IRQ_32).long       __dabt_svc                       @  3  (SVC_26 / SVC_32).long       __dabt_invalid                     @  4.long       __dabt_invalid                     @  5.long       __dabt_invalid                     @  6.long       __dabt_invalid                     @  7.long       __dabt_invalid                     @  8.long       __dabt_invalid                     @  9.long       __dabt_invalid                     @  a.long       __dabt_invalid                     @  b.long       __dabt_invalid                     @  c.long       __dabt_invalid                     @  d.long       __dabt_invalid                     @  e.long       __dabt_invalid                     @  f

vector_stub是一个宏界说:

.macro     vector_stub, name, mode, correction=0.align      5vector_\name:.if \correctionsub  lr, lr, #\correction.endif@@ Save r0, lr_ (parent PC) and spsr_@ (parent CPSR)@stmia       sp, {r0, lr}             @ save r0, lrmrs  lr, spsr                          @ 保存跳转之前的CPSR到lr寄存器str    lr, [sp, #8]                    @ save spsr@@ Prepare for SVC32 mode.  IRQs remain disabled.@mrs  r0, cpsreor   r0, r0, #(\mode ^ SVC_MODE)msr  spsr_cxsf, r0                 @ 预备进入svc形式@@ the branch table must immediately follow this code@and  lr, lr, #0x0f                    @ 得到跳转前所在的形式(usr、svr等)mov r0, spldr   lr, [pc, lr, lsl #2]            @ 依据形式跳转到相应的data abort指令,并进入svc形式movs       pc, lr                     @ branch to handler in SVC modeENDPROC(vector_\name).endm

由代码中赤色标示部分可看出,关于同一个反常,依据进入反常之前所在的形式,会跳转到不同的指令分支,www.linuxidc.com这些指令分支紧跟在vector_stub宏界说的后边。假如进入data abort之前处于usr形式,那么跳转到__dabt_usr;假如处于svc形式,那么跳转到__dabt_svc;不然跳转到__dabt_invalid。
实际上,进入反常向量前Linux只能处于usr或许svc两种形式之一。这时由于irq等反常在跳转表中都要经过vector_stub宏,而不论之前是哪种状况,这个宏都会将CPU状况改为svc形式。
usr形式即Linux中的用户态形式,svc即内核形式。
下面看一下在不同形式下进入data abort时的处理进程。

2 svc形式进入data abort

svc形式进入data abort,也便是Linux的内核形式进入data aboart时,会跳转到__dabt_svc。

__dabt_svc:svc_entry               @ 维护寄存器现场mrs  r9, cpsrtst    r3, #PSR_I_BIT            @ 查看是否要开中止biceq       r9, r9, #PSR_I_BITbl    CPU_DABORT_HANDLER  @ 处理反常之前的预备作业msr  cpsr_c, r9mov r2, spbl    do_DataAbort        @ 首要操作都在这儿,本文暂不研讨disable_irqldr   r0, [sp, #S_PSR]msr  spsr_cxsf, r0ldmia      sp, {r0 - pc}^                @ load r0 - pc, cpsrENDPROC(__dabt_svc)

CPU_DABORT_HANDLER的界说在glue.h:

#define CPU_DABORT_HANDLER v6_early_abort

关于s3c6410,v6_early_abort的界说在abort-ev6.S中,里边涉及到许多ARM的细节操作,但对咱们来说,只需求了解下面这两句即可:

mrc  p15, 0, r1, c5, c0, 0              @ get FSRmrc  p15, 0, r0, c6, c0, 0              @ get FAR

这两句用于读取协处理器CP15的C5、C6寄存器。当data abort反常发生时,C5寄存器中保存的值指明晰是哪种原因导致的反常,详细原因可在介绍arm的材料中找到。C6寄存器中保存的是导致data abort的存储地址。

3 usr形式进入data abort

usr形式进入data abort,也便是Linux的用户形式进入data bort时,会跳转到__dabt_usr。

__dabt_usr:usr_entry                                    @ 维护寄存器现场kuser_cmpxchg_checkbl    CPU_DABORT_HANDLER  @ 与svc形式时处理进程相同enable_irq                                  @ 开中止mov r2, spadr  lr, ret_from_exception            @ 重设回来地址b     do_DataAbort                      @ 与svc形式时处理进程相同ENDPROC(__dabt_usr)

由代码可知,用户形式和内核形式的data abort处理进程相似,差异在于:

  • 用户形式下data abort处理一定是开中止的;内核形式下则由详细状况决议。
  • 用户形式下反常处理回来地址被设为ret_from_exception (entry-armv.S文件);内核形式下则回来到出现反常的那条句子。

下面看一下ret_from_exception:

ENTRY(ret_from_exception)get_thread_info tskmov why, #0b     ret_to_userENDPROC(__pabt_usr)

ret_to_user会判别是否需求进行进程调度,并终究回来到用户空间。用户空间data abort时或许发生进程调度的原因就在这儿。

4 未界说状况的data abort

除了usr和svc形式之外,其它形式下发生data abort时,都会调用__dabt_invalid函数。这儿所说的其它形式在linux正常运转进程中是不应该存在的,所以假如进入__dabt_invalid函数,那就代表Linux内核应该溃散了。

__dabt_invalid:inv_entry BAD_DATAb     common_invalidENDPROC(__dabt_invalid)

inv_entry宏做的首要作业是保存寄存器现场(压栈)。
common_invalid做一些必要的设置,终究调用C函数bad_mode (traps.c)。

asmlinkage void bad_mode(struct pt_regs *regs, int reason){console_verbose();printk(KERN_CRIT "Bad mode in %s handler detected\n", handler[reason]);die("Oops - bad mode", regs, 0);local_irq_disable();panic("bad mode");}

由代码可知,bad_mode首要是输出一些必要的信息,然后调用panic函数,进入死循环。
上文说到data abort的正常处理进程中,终究会调用do_DataAbort函数,下面剖析一下该函数的处理进程。

5 do_DataAbort

asmlinkage void __exception do_DataAbort(unsigned long addr,                     // 导致反常的内存地址unsigned int fsr,                          // 反常发生时CP15中的寄存器值,见前文struct pt_regs *regs)                     // 反常发生时的寄存器值列表{     const struct fsr_info *inf = fsr_info + (fsr & 15) + ((fsr & (1 << 10)) >> 6);if (!inf->fn(addr, fsr, regs))return;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);}

处理data abort时,首要依据fsr的值得到发生abort的原因,然后依据此原因从一个大局数组fsr_info中得到处理此种abort的struct fsr_info结构,然后调用结构中的fn函数处理。假如fn函数为空,或许函数回来不为0,则调用arm_notify_die函数。

5.1 arm_notify_die

首要看一下比较简略的景象,即fsr_info中fn未界说,此刻调用arm_notify_die处理:

void arm_notify_die(const char *str, struct pt_regs *regs,struct siginfo *info, unsigned long err, unsigned long trap){if (user_mode(regs)) {// 。。。force_sig_info(info->si_signo, info, current);} else {die(str, regs, err);}}

该函数首要运用user_mode判别abort时是归于用户形式仍是内核形式,判别办法是看cpsr寄存器中的形式位。依照arm的界说,形式位为0代表用户形式。

  • 假如是用户形式,那么强制发送一个信号给导致abort的使命(留意这儿的使命或许是一个线程)。详细哪个信号被发送由struct fsr_info结构体中界说的值决议,一般来说,是一个能使进程中止的信号,比方SIGSEGV等等(SIGSEGV之类的信号即便被发给一个线程,也会中止整个进程,详细可看get_signal_to_deliver函数)。
  • 假如是内核形式,那么调用die函数,这是kernel处理OOPS的规范函数。

5.2 fsr_info

fsr_info数组界说在fault.c中,关于每一种或许导致data abort的原因,都有一个fsr_info结构与之对应。

static struct fsr_info fsr_info[] = {{ do_bad,              SIGSEGV, 0,         "vector exception"            },// 。。。{ 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"             },{ do_bad,              SIGBUS,  0,         "unknown 16"                  },// 。。。{ 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将作独自处理:

  • “section translation fault” do_translation_fault

段转化过错,即找不到二级页表

  • “page translation fault” do_page_fault

页表过错,即线性地址无效,没有对应的物理地址

  • “section permission fault” do_sect_fault

段权限过错,即二级页表权限过错

  • “page permission fault” do_page_fault

页权限过错

5.3 段权限过错 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则比较复杂:

  • 调用fixup_exception进行修正操作,fixup的详细细节可在内核文档exception.txt中找到,它可用于处理get_user之类函数传入的地址参数无效的状况。
  • 假如不能修正,调用die函数处理oops。
  • 假如没有进程上下文,内核会在上一步的oops中panic。所以到这儿必定有一个进程与之相关,所以调用do_exit(SIGKILL)函数退出进程,SIGKILL会被设置在task_struct的exit_code域。

5.4 段表过错 do_translation_fault

do_translation_fault函数中,会首要判别引起abort的地址是否处于用户空间。

  • 假如是用户空间地址,调用do_page_fault,转入和页表过错、页权限过错相同的处理流程。
  • 假如是内核空间地址,会判别该地址对应的二级页表指针是否在init_mm中。假如在init_mm里边,www.linuxidc.com那么该二级页表指针到当时进程的一级页表;不然,调用do_bad_area处理(或许会调用到fixup)。

对段表过错的处理逻辑的个人了解如下(不确保彻底精确):
Linux发生段表过错,除了fixup之外还有两种原因:一个是用户空间映射的线性地址出现反常,另一个是内核中调用vmalloc分配的线性地址出现反常。对用户空间地址的反常处理很简单了解。关于内核地址,从vmalloc的完成代码中能够看到,它分配的线性空间的映射联系都会保存到大局变量init_mm中,所以,任何vmalloc生成的线性空间的二级页表都应该在init_mm中找到。(init_mm是内核的mm_struct,办理整个内核的内存映射)。
从这儿也能够看出,对vmalloc的地址拜访或许会发生两次反常:第一次是段表过错,生成二级页表;第2次是页表过错,分配真实的物理页面到线性空间。

5.5 页表过错 do_page_fault
5.6 页权限过错 do_page_fault

do_page_fault完成了真实的物理页面分配作业,别的栈扩展、mmap的支撑等也都在这儿。关于物理页面的分配,会调用到do_anonymous_page->。。。-> __rmqueue,__rmqueue中完成了物理页面分配的同伴算法。
假如当时没有满意物理页面供内存分配,即分配失利:

  • 内核形式下的abort会调用__do_kernel_fault,这与段权限过错中的处理相同。
  • 用户形式下,会调用do_group_exit退出该使命所属的进程。

用户程序请求内存空间时,假如库函数自身的内存池不能满意分配,会调用brk体系调用向体系请求扩展堆空间。但此刻扩展的仅仅线性空间,直到真实运用到那块线性空间时,体系才会经过data abort分配物理页面。所以,malloc回来不为NULL只能阐明得到了线性空间的资源,真实物理内存分配失利时,进程仍是会以资源缺乏为由,直接退出。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/qiche/xinnengyuan/277214.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部