ARM Linux异常处理之data abort

发布者:SecretWhisper最新更新时间:2016-06-20 来源: eefocus关键字:ARM  Linux  异常处理  data  abort 手机看文章 扫描二维码
随时随地手机看文章

1 异常向量与程序跳转

data abort是ARM体系定义的异常之一。异常发生时,ARM会自动跳转到异常向量表中,通过向量表中的跳转命令跳转到相应的异常处理中去。
ARM的异常处理向量表在entry-armv.S文件中:
       .globl      __vectors_start
__vectors_start:
       swi   SYS_ERROR0
       b     vector_und + stubs_offset
       ldr   pc, .LCvswi + stubs_offset
       b     vector_pabt + stubs_offset
       b     vector_dabt + stubs_offset
       b     vector_addrexcptn + stubs_offset
       b     vector_irq + stubs_offset
       b     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      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                          @ 保存跳转之前的CPSR到lr寄存器
       str    lr, [sp, #8]                    @ save spsr
 
       @
       @ Prepare for SVC32 mode.  IRQs remain disabled.
       @
       mrs  r0, cpsr
       eor   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, sp
       ldr   lr, [pc, lr, lsl #2]            @ 根据模式跳转到相应的data abort指令,并进入svc模式
       movs       pc, lr                     @ branch to handler in SVC mode
ENDPROC(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, 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        @ 主要操作都在这里,本文暂不研究
 
       disable_irq
 
       ldr   r0, [sp, #S_PSR]
       msr  spsr_cxsf, r0
       ldmia      sp, {r0 - pc}^                @ load r0 - pc, cpsr
ENDPROC(__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 FSR
       mrc  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_check
 
       bl    CPU_DABORT_HANDLER  @ 与svc模式时处理过程一样
 
       enable_irq                                  @ 开中断
       mov r2, sp
       adr  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 tsk
       mov why, #0
       b     ret_to_user
ENDPROC(__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_DATA
       b     common_invalid
ENDPROC(__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的地址访问可能会产生两次异常:第一次是段表错误,生成二级页表;第二次是页表错误,分配真正的物理页面到线性空间。

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只能说明得到了线性空间的资源,真正物理内存分配失败时,进程还是会以资源不足为由,直接退出。
关键字:ARM  Linux  异常处理  data  abort 引用地址:ARM Linux异常处理之data abort

上一篇:linux2.6.26内核中ARM中断实现详解
下一篇:ARM Linux异常处理之data abort二

推荐阅读最新更新时间:2024-03-16 14:57

ARM cortex-M3 异常处理分析
一、进入异常之前处理器可能的状态有: 1.handler 2.线程,MSP 3.线程,PSP 二、产生异常时: 1、有一个压栈的过程,产生异常时使用PSP,就压入到PSP中,产生异常时使用MSP,就压入到MSP中 2、会根据处理器的模式和使用的堆栈,设置LR的值(当然设置完的LR的值再压栈) 三、异常返回时: 根据LR的值,判读使用那个堆栈,然后再从相应的堆栈中POP数据到寄存器。 举例说明: 在利用OSStartHighRdy- OSPendSV- OSPendSV_nosave启动第一个线程时,在异常进入的时候,压栈到MSP(不会影响PSP的内容),在通过BX LR指令从异常返回之前,有一句ORR LR, LR,
[单片机]
基于S3C6410的ARM11学习(十一) DDR初始化
之前的程序都是在内部的stepping stone中运行的。但是stepping stone的大小是很小的。ARM11只有8K的大小。这么小的空间,要运行大型程序肯定是不行的。所以就需要外部的存储器。ARM11的外部存储器用的是ddr。所以就要对ddr进行初始化,同时将代码拷贝到ddr中。 先看看S3C6410以nandflash的启动。在上电的时候,CPU会自动把外部的nandflash的前8k数据拷贝到内部的stepping stone中。Stepping stone的地址是从0x0c000000开始的。然后CPU将stepping stone给映射到0x00000000地址处,然后CPU跳转到0地址处执行第一条代
[单片机]
基于S3C6410的<font color='red'>ARM</font>11学习(十一) DDR初始化
PPP拨号在ARM2410平台上的移植
本次PPP移植是在三星arm2410的平台上进行的,所用的工作环境为:   Kernel: 2.4.18Sysvinit:2.86Tinylogin:1.4Glibc: 2.2.5CC:3.3PPP: 2.4.1U-boot:1.1.3一、具体操作步骤首先编译内核支持 PPP:   $make menuconfig l$make ARCH=arm CROSS_COMPILE=/../arm-linux- zImage$./mkimage2410 uImage其中mkimage2410文件来自u-boot1.1.1 需要根据你的路径修改mkimage2410的内容,指定mkimage文件的位置。   第二步,制作文件系统:   1.
[嵌入式]
ARM推出Cortex-A12 意欲为何?
    ARM在2013年的COMPUTEX展前记者会,针对中价位的行动运算市场推出一款全新的Cortex-A12,同时针对所谓的「中价位」市场,还另行推出GPU IP Mali-T622与视讯IP Mali-V500产品线,希望透过完整的IP组合来满足主流价位的智慧型手机与平板电脑。 附图 : Cortex-A12系统架构图 BigPic:600x502 此举一出,对于全球行动运算产业的未来发展,无疑是投下一个未知的变数。ARM在推出Cortex-A15后,紧接又推出A7处理器核心,再以big-Little架构的方式,希望能为客户在效能与功耗上取得一个最佳的平衡点,虽然ARM在应用处理器核心的搭配上,一向是采取尊重客户的设计理
[手机便携]
ARM抢英特尔市场 人工智能大战即将爆发
  抢攻 英特尔 独占的服务器市场,在手机领域大放异彩的芯片业者 ARM 触角再向外延伸,其和微软(Microsoft)、惠与科技等大厂合作,目标至2021年市占从零提高到25%, ARM 与 英特尔 的人工智能(AI)战争即将开打。下面就随网络通信小编一起来了解一下相关内容吧。   HPE、微软和 英特尔 在服务器市场是盟友关系,微软与英特尔的合作,使得PC进入主流市场;HPE前身HP,和英特尔携手研发64bit处理器Itanium,陆续从大型服务器到PC都采用英特尔制的处理器,如今双双发布采用 ARM 架构的消息。   ARM早在2011年就曾进军服务器市场,Calxeda于2011年推出搭载ARM处理器核心的低功耗服务器,
[网络通信]
基于ARM嵌入式系统的数字音频播放系统设计
  0 引言   MPEG(Moving Picture Experts Group)是运动图像专家组的英文缩写。MP3是MPEG Audio Layer-3的缩写,即MPEG第3层音频编码标准,使用MP3标准对音频数据编码既可以获得较大的音乐数据压缩比,又可以得到较好的音乐回放质量。国内外现有的MP3解码方案实现有2种方案:硬件和软件解码。利用专用解码芯片的硬件解码,其灵活性不好,并且硬件解码芯片的价格昂贵。基于DSP或ARM等处理器开发平台的软件解码,扩展新能好,性价比高。   ARM(Advanced RISC Machines)既可以认为是一个公司的名字,也可以认为是对一类微处理器的通称,还可以认为是一种技术。目前,采
[单片机]
基于<font color='red'>ARM</font>嵌入式系统的数字音频播放系统设计
arm 驱动linux内核驱动之中断下半部编程
本文部分参考华清远见文档 中断上半部要求执行时间间隔段,所以往往将处理时间较长的代码放在中断下半部来处理 中断下半部的应用:网卡驱动上半部初始化网卡驱动等短时间的事件,下半部收发数据 中断下半部: a, 下半部产生的原因: 1,中断上下文中不能阻塞,这也限制了中断上下文中能干的事 2,中断处理函数执行过程中仍有可能被其他中断打断,都希望中断处理函数执行得越快越好。 基于上面的原因,内核将整个的中断处理流程分为了上半部和下半部。上半部就是之前所说的中断处理函数,它能最快的响应中断,并且做一些必须在中断响应之后马上要做的事情。而一些需要在中断处理函数后继续执行的操作,内核建议把它放在下半部执行。 比如:在linux内核中,
[单片机]
Actel为嵌入ARM7功能的FPGA推出开发工具套装
Actel公司宣布推出CoreMP7开发工具套装,这是完整的软、硬件开发环境,针对该公司CoreMP7软ARM7处理器内核的执行而设。CoreMP7开发工具套装包含屡获殊荣的CoreMP7、具Actel ARM7功能的M7 ProASIC3器件和FPGA开发工具,为用户提供了一切所需工具,可以快速和简便地评估及设计以FPGA为基础的系统级芯片 (SoC) 应用。 Actel IP市务经理Mike Thompson称:“CoreMP7开发工具充分发挥了FPGA的灵活性结合快速推向市场和业界标准ARM7处理器技术的优势,简化了复杂SoC芯片的评估和开发工作。这套开发工具是一个完善的开发环境,包含构件组装、行为仿真、电路综合、布局、编
[新品]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved