zoukankan      html  css  js  c++  java
  • ARM Linux异常处理之data abort(二)【转】

    转自:https://blog.csdn.net/walkingman321/article/details/6238608

    上文提到data abort的正常处理过程中,最终会调用do_DataAbort函数,下面分析一下该函数的处理过程。

    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函数。

    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代表用户模式。

    l        如果是用户模式,那么强制发送一个信号给导致abort的任务(注意这里的任务可能是一个线程)。具体哪个信号被发送由struct fsr_info结构体中定义的值决定,一般来说,是一个能使进程停止的信号,比如SIGSEGV等等(SIGSEGV之类的信号即使被发给一个线程,也会停止整个进程,具体可看get_signal_to_deliver函数)。

    l        如果是内核模式,那么调用die函数,这是kernel处理OOPS的标准函数。

    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将作单独处理:

    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只能说明得到了线性空间的资源,但物理内存可能并没有映射上去,所以真正物理内存分配失败时,进程还是会以资源不足为由,直接退出。

    【作者】张昺华
    【大饼教你学系列】https://edu.csdn.net/course/detail/10393
    【新浪微博】 张昺华--sky
    【twitter】 @sky2030_
    【微信公众号】 张昺华
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    iOS8之后,UITableViewRowAction实现滑动多个按钮
    关于UINavigationController的一些技巧
    NSRegularExpression 使用
    UIWindow
    SVN:The working copy is locked due to a previous error (二)
    iOS监听电话来电、挂断、拨号等
    UIDeviceOrientation 和 UIInterfaceOrientation
    java_day03
    java_day 02
    java_day_02
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/14373436.html
Copyright © 2011-2022 走看看