Linux内核ARM架构异常中断向量表

  • 说明
  • ARM中异常中断的种类
  • ARM异常中断向量表
  • 内核异常向量表
    • 异常向量表跳转
      • vector_srub宏
    • 内核启动建立异常向量表

  当异常中断发生时,系统执行完当前指令后,将跳转到相应的异常中断处理程序处执行。在异常中断处理程序执行完成后,程序返回到发生中断的指令的下一条指令处执行。

说明

Kernel版本:4.14.111
ARM处理器,Contex-A7

ARM中异常中断的种类

  ARM体系中的异常中断下图所示

ARM异常中断向量表


  ARM的异常中断向量表可以是高端向量表,也可以是低端向量表,两者取其一。区别是基地址不同。高端向量是ARM架构可选配置,可以通过硬件外部输入管脚来配置是低端向量还是高端向量,不能通过指令来改变向量的位置,但如果ARM芯片内部有标准ARM协处理器,那么协处理器CP15的寄存器C1的bit13可以用来切换低端和高端向量地址,等于0时为低端向量,等于1时为高端向量。
  Linux内核分用户空间、内核空间,通常32位处理器,用户空间0-3G,内核空间3-4G,所以Linux内核使用高端向量表。

内核异常向量表

  Linux内核在链接时,_vectors_start和__vectors_end之间保存了异常向量表。__stubs_start和__stubs_end 保存了异常处理的函数。查看链接文件vmlinux.lds.S文件,可以看到:

 /** The vectors and stubs are relocatable code, and the* only thing that matters is their relative offsets*/__vectors_start = .;.vectors 0xffff0000 : AT(__vectors_start) {            (1)*(.vectors)}. = __vectors_start + SIZEOF(.vectors);__vectors_end = .;__stubs_start = .;.stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) {*(.stubs)                                        (2)}. = __stubs_start + SIZEOF(.stubs);__stubs_end = .;

1)链接时,将.vectors段内容链接到虚拟地址0xffff0000地址。(这里我理解为在vmlinux镜像中.vectors段连续,夹在__vectors_start和__vectors_end 中间,但是链接的虚拟地址指向0xffff0000)
2)同上。
  arch/arm/kernel/entry-armv.S 中.vectors段保存了异常向量表。

 .section .vectors, "ax", %progbits
.L__vectors_start:W(b)  vector_rstW(b)  vector_undW(ldr)    pc, .L__vectors_start + 0x1000W(b) vector_pabtW(b) vector_dabtW(b) vector_addrexcptnW(b)   vector_irqW(b)  vector_fiq

  异常向量表,相当于保存了发生异常情况时,需要跳转的指令。
  考虑一个问题,为什么硬件跳转异常向量表,如vector_irq,就会跳转到vector_irq标号处执行;为什么能跳到呢?相当于函数指针效果?
  看一下b跳转的指令格式:
bit[31:28]:条件码
bit[27:25]:101
bit24:是否链接标识
bit[23:0]:跳转的偏移量
  b跳转是一个相对跳转,依赖于当前的PC值和label相对于当前PC值的偏移量,这个偏移量在编译链接的时候就已经确定了,会存在b跳转指令机器码的bit[23:0],是24bit有符号数;因为ARM指令是word对齐的,最低2bit永远为0;所以左移两位后表示有效偏移的而是26bit的有符号数,也就是可以向前和向后都可以跳转32MB的范围。
  vmlinux.lds.S中,链接时的虚拟地址已经确定了,当建立完页面映射,异常向量表拷贝后,当异常来临时,就能通过向量表找到正确的label处执行了。

异常向量表跳转

  根据上文异常向量表,有异常发生时,就可以跳转了,但是跳转到哪里呢?有些函数在代码中找不到。比如vector_irq。

vector_srub宏

 .macro  vector_stub, name, mode, correction=0.align    5vector_\name:                  //定义了一个vector_name的label,如果参数name是irq,那就是vector_irq  .if \correctionsub   lr, lr, #\correction    //如果要修正lr PC指针,它是返回地址 .endif@@ Save r0, lr_<exception> (parent PC) and spsr_<exception>@ (parent CPSR)@stmia sp, {r0, lr}        @ save r0, lr          //r0 lr入栈mrs    lr, spsr                                        //此刻spsr值为。str  lr, [sp, #8]        @ save spsr            //保存spsr寄存器值(中断前的cpsr)此时irq栈的内容为r0,lr,cpsr@@ Prepare for SVC32 mode.  IRQs remain disabled.@mrs    r0, cpsr                                        //cpsr寄存器赋值给r0eor   r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)     //设置处理器模式,切换到svc32模式msr  spsr_cxsf, r0                           //处理后的r0值赋值给spsr寄存器@@ the branch table must immediately follow this code@and lr, lr, #0x0f                           //中断前的cpsr已保存到lr,获取中断前的模式,usr or svcTHUMB(    adr r0, 1f          )THUMB( ldr lr, [r0, lr, lsl #2]    )mov    r0, sp                                  //sp保存到r0,切换模式后sp会变成对应模式的sp,所以这里要保存ARM(   ldr lr, [pc, lr, lsl #2]    )           //根据lr获取的中断前的模式,左移两位,获取偏移量。在加上当前pc位置,得到新的lr,即中断处理函数地址。movs  pc, lr          @ branch to handler in SVC mode        // irq/fiq中断向量表正好紧接当前指令之后,即pc等价于irq/fiq中断向量表基地址,//lr为中断前模式,pc + lr * 4即得到对应模式的中断入口函数地址,//例如__irq_usr、__irq_svc,从不同模式进入中断,处理流程有所不同,此处跳转到对应模式的中断处理程序//在PC指针跳转的时候,会切换到svc32模式。
ENDPROC(vector_\name).align 2@ handler addresses follow this label
1:.endm

  下面看一下.stub段包含的内容:

 .section .stubs, "ax", %progbits@ This must be the first word.word   vector_swivector_rst:ARM(   swi SYS_ERROR0  )THUMB( svc #0      )THUMB( nop         )b  vector_und/** Interrupt dispatcher*/vector_stub irq, IRQ_MODE, 4.long   __irq_usr           @  0  (USR_26 / USR_32).long   __irq_invalid           @  1  (FIQ_26 / FIQ_32).long   __irq_invalid           @  2  (IRQ_26 / IRQ_32).long   __irq_svc           @  3  (SVC_26 / SVC_32).long   __irq_invalid           @  4.long  __irq_invalid           @  5.long  __irq_invalid           @  6.long  __irq_invalid           @  7.long  __irq_invalid           @  8.long  __irq_invalid           @  9.long  __irq_invalid           @  a.long  __irq_invalid           @  b.long  __irq_invalid           @  c.long  __irq_invalid           @  d.long  __irq_invalid           @  e.long  __irq_invalid           @  f/** Data abort dispatcher* Enter in ABT mode, spsr = USR CPSR, lr = USR PC*/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/** Prefetch abort dispatcher* Enter in ABT mode, spsr = USR CPSR, lr = USR PC*/vector_stub  pabt, ABT_MODE, 4.long  __pabt_usr          @  0 (USR_26 / USR_32).long    __pabt_invalid          @  1 (FIQ_26 / FIQ_32).long    __pabt_invalid          @  2 (IRQ_26 / IRQ_32).long    __pabt_svc          @  3 (SVC_26 / SVC_32).long    __pabt_invalid          @  4.long  __pabt_invalid          @  5.long  __pabt_invalid          @  6.long  __pabt_invalid          @  7.long  __pabt_invalid          @  8.long  __pabt_invalid          @  9.long  __pabt_invalid          @  a.long  __pabt_invalid          @  b.long  __pabt_invalid          @  c.long  __pabt_invalid          @  d.long  __pabt_invalid          @  e.long  __pabt_invalid          @  f/** Undef instr entry dispatcher* Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC*/vector_stub   und, UND_MODE.long  __und_usr           @  0 (USR_26 / USR_32).long    __und_invalid           @  1 (FIQ_26 / FIQ_32).long    __und_invalid           @  2 (IRQ_26 / IRQ_32).long    __und_svc           @  3 (SVC_26 / SVC_32).long    __und_invalid           @  4.long  __und_invalid           @  5.long  __und_invalid           @  6.long  __und_invalid           @  7.long  __und_invalid           @  8.long  __und_invalid           @  9.long  __und_invalid           @  a.long  __und_invalid           @  b.long  __und_invalid           @  c.long  __und_invalid           @  d.long  __und_invalid           @  e.long  __und_invalid           @  f.align 5/*=============================================================================* Address exception handler*-----------------------------------------------------------------------------* These aren't too critical.* (they're not supposed to happen, and won't happen in 32-bit data mode).*/vector_addrexcptn:b vector_addrexcptn/*=============================================================================* FIQ "NMI" handler*-----------------------------------------------------------------------------* Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86* systems.*/vector_stub  fiq, FIQ_MODE, 4.long   __fiq_usr           @  0  (USR_26 / USR_32).long   __fiq_svc           @  1  (FIQ_26 / FIQ_32).long   __fiq_svc           @  2  (IRQ_26 / IRQ_32).long   __fiq_svc           @  3  (SVC_26 / SVC_32).long   __fiq_svc           @  4.long  __fiq_svc           @  5.long  __fiq_svc           @  6.long  __fiq_abt           @  7.long  __fiq_svc           @  8.long  __fiq_svc           @  9.long  __fiq_svc           @  a.long  __fiq_svc           @  b.long  __fiq_svc           @  c.long  __fiq_svc           @  d.long  __fiq_svc           @  e.long  __fiq_svc           @  f.globl vector_fiq

  当用户空间(usr模式)发生外部中断时,会跳转到__irq_usr处执行,内核空间(svc模式)发生异常中断时,会跳转到__irq_svc处执行。
  除了fiq外,每个异常向量只有usr和svc有入口,而其他都是invalid,是因为linux只会从usr(application)和svc(kernel)两种mode跳转到exception。为什么只会从这两种mode跳转呢?因为linux异常前的状态;要么是内核态处于svc模式,执行__xxx_svc代码;要么是用户态处于usr模式,执行__xxx_usr代码。
  vector_rst和vector_swi比较特殊,没有使用vector_stub宏定义。
  rst说明是系统出错,用软件中断SYS_ERROR0来处理;
  swi是跳到软中断,vector_swi在arch/arm/kernel/entry-common.S中实现,主要是系统调用相关。这里就不展开描述了。

注意:arm的8中异常向量和7种工作模式不是一一对应的,但是存在关联。向量0是reset,如果是cpu运行到了向量0说明是系统出错,用软件中断SYS_ERROR0来处理;向量2也是跳到软中断;软中断会陷入svc模式。向量3和4都会陷入abt模式。在调用vector_stub 宏时,都已经提前设定好了。

内核启动建立异常向量表

  从上文描述可以看到,内核异常向量表在0xffff0000-0xffff0fff这4KB空间,具体处理函数在0xffff1000-0xffff1fff。从System.map中也可以看到处理函数符号

  那么内核启动过程中,必然要给这2个page分配物理内存,建立映射,并把vmlinux中的异常向量表拷贝到对应的物理页面中。
start_kernel->setup_arch->paging_init->devicemaps_init

static void __init devicemaps_init(const struct machine_desc *mdesc)
{struct map_desc map;unsigned long addr;void *vectors;/** Allocate the vector page early.*/vectors = early_alloc(PAGE_SIZE * 2);       (1)early_trap_init(vectors);                    (2)...

1)申请2个page大小内存
2)调用early_trap_init拷贝异常向量表和处理函数
start_kernel->setup_arch->paging_init->devicemaps_init->early_trap_init

void __init early_trap_init(void *vectors_base)
{#ifndef CONFIG_CPU_V7Munsigned long vectors = (unsigned long)vectors_base;extern char __stubs_start[], __stubs_end[];extern char __vectors_start[], __vectors_end[];unsigned i;vectors_page = vectors_base;/** Poison the vectors page with an undefined instruction.  This* instruction is chosen to be undefined for both ARM and Thumb* ISAs.  The Thumb version is an undefined instruction with a* branch back to the undefined instruction.*/for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)((u32 *)vectors_base)[i] = 0xe7fddef1;/** Copy the vectors, stubs and kuser helpers (in entry-armv.S)* into the vector page, mapped at 0xffff0000, and ensure these* are visible to the instruction stream.*/memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);    //拷贝内核当前__vectors_end - __vectors_start中间内容到申请的第一个页面中。即异常向量表。未拷贝前向量表存在内核镜像中。memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);//拷贝内核当前__stubs_end - __stubs_start中间内容到申请的第二个的页面中。即异常向量处理函数。kuser_init(vectors_base);flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
#else /* ifndef CONFIG_CPU_V7M *//** on V7-M there is no need to copy the vector table to a dedicated* memory area. The address is configurable and so a table in the kernel* image can be used.*/
#endif
}

  完成拷贝异常向量表和处理函数拷贝后,我们知道,申请的内存虚拟地址是随机的,不可能是我们需要的地址0xffff0000。所以需要对这两个页面进行映射,映射到0xffff0000开始的地址。所以继续来看devicemaps_init函数。

static void __init devicemaps_init(const struct machine_desc *mdesc)
{struct map_desc map;unsigned long addr;void *vectors;/** Allocate the vector page early.*/vectors = early_alloc(PAGE_SIZE * 2);       (1)early_trap_init(vectors);                    (2).../** Create a mapping for the machine vectors at the high-vectors* location (0xffff0000).  If we aren't using high-vectors, also* create a mapping at the low-vectors virtual address.*/map.pfn = __phys_to_pfn(virt_to_phys(vectors));map.virtual = 0xffff0000;map.length = PAGE_SIZE;
#ifdef CONFIG_KUSER_HELPERSmap.type = MT_HIGH_VECTORS;
#elsemap.type = MT_LOW_VECTORS;
#endifcreate_mapping(&map);                 //将vectors页面映射到0xffff0000if (!vectors_high()) {map.virtual = 0;map.length = PAGE_SIZE * 2;map.type = MT_LOW_VECTORS;create_mapping(&map);}/* Now create a kernel read-only mapping */map.pfn += 1;map.virtual = 0xffff0000 + PAGE_SIZE;map.length = PAGE_SIZE;map.type = MT_LOW_VECTORS;create_mapping(&map);                  //将stubs页面映射到0xffff1000地址。
}

  拷贝完成后,当异常发生时,硬件跳转到异常向量表地址,就不会发生找不到页表的情况了。

Linux内核ARM架构异常中断向量表相关推荐

  1. linux内核学习10.1:Linux内核ARM7架构异常中断向量表

    参考:https://www.cnblogs.com/douzi2/p/5112743.html 当异常中断发生时,系统执行完当前指令后,将跳转到相应的异常中断处理程序处执行.在异常中断处理程序执行完 ...

  2. 【Linux 内核】Linux 内核体系架构 ( 硬件层面 | 内核空间 | 用户空间 | 内核态与用户态切换 | 系统调用 | 体系结构抽象层 )

    文章目录 一.Linux 内核体系架构 二.内核态与用户态切换 ( 系统调用层 ) 三.体系结构抽象层 一.Linux 内核体系架构 Linux 内核最初的源码不足一万行 , 当前的 Linux 内核 ...

  3. 《铜豌豆 Linux》 ARM 架构 11.5.2 版本发布

    https://www.atzlinux.com/News/2022/20221026.htm <铜豌豆 Linux> ARM 架构 11.5.2 版本发布 2022-10-26 2022 ...

  4. Linux内核系统架构介绍

    28年前(1991年8月26日)Linus公开Linux的代码,开启了一个伟大的时代.这篇文章从进程调度,内存管理,设备驱动,文件系统,网络等方面讲解Linux内核系统架构.Linux的系统架构是一个 ...

  5. Linux 内核系统架构

    描述Linux内核的文章已经有上亿字了 但是对于初学者,还是应该多学习多看,毕竟上亿字不能一下子就明白的. 即使看了所有的Linux 内核文章,估计也还不是很明白,这时候,还是需要fucking th ...

  6. 制造linux内核异常,了解Linux内核中的异常

    我想在非常低的温度下调试我们的嵌入式Linux系统(< 40C).问题是,它并不总是正确启动,我试图找出原因.经过一番分析,我看到内核启动了下面的输出期间进入恐慌:了解Linux内核中的异常 c ...

  7. linux支持arm架构么_全球首次!中国推出一款支持X86、ARM等各种芯片架构的操作系统...

    众所周知,说起操作系统,大家都知道windows是最强大的,目前占了全球85%左右的份额.而windows之所以强大,一是推出时间早,二是因为wintel联盟. windows在早期和intel形成绑 ...

  8. Linux内核中断和异常分析(上)

    中断,通常被定义为一个事件.打个比方,你烧热水,水沸腾了,这时候你要去关掉烧热水的电磁炉,然后再去办之前手中停不下来的事情.那么热水沸腾就是打断你正常工作的一个信号机制.当然,还有其它的情况,我们以后 ...

  9. linux内核 arm交叉编译

    我的Ubuntu版本是14.04 1.在官网下载Linux内核源码     官网地址:https://www.kernel.org/ 2.解压Linux内核源码 3.安装arm-gcc交叉编译工具链: ...

最新文章

  1. SQL Server 2005 处理交叉表
  2. Leaflet中使用awesome-markers插件显示带图标的marker
  3. 【算法基础】漫画:什么是 “跳表” ?
  4. Eclipse启动Tomcat时45秒超时的解决方法
  5. 无法向会话状态服务器发出回话状态请求
  6. (转)终于把区块链的技术与应用讲清楚了ppt
  7. Java生成Word的报告模板
  8. 《纽约时报》畅销书作家发布新的COVID安全旅行提示电子书
  9. dw如何制作图片自动切换效果_DW制作自动切换图js代码
  10. 网络安全阶段一学习笔记
  11. veeam 备份文件服务器,如何用veeam给windows服务器做备份?
  12. 如何输入版权符号 copyright
  13. 网站服务器停止运行,服务器已停止响应是怎么回事
  14. 如梦若梦,肾盂肾炎似烟火
  15. to_date() 整理
  16. 解读常见传感器的CFA排列(彩色滤色矩阵,Color Filter Array)
  17. 在本计算机无法启动用友通服务器,用友通客户端连接不上服务器解决方案
  18. 高等数学(导数的应用)
  19. java工具类获取文件扩展名与content-type、http与content-type映射关系
  20. 苹果手机用什么软件测试续航,iOS 14.6负优化:测试发现7款iPhone机型的续航均下滑...

热门文章

  1. 快速记忆23种设计模式
  2. Jmeter 证书导入
  3. Window thin PC的安装与汉化
  4. linux重定向logcat,logcat重定向adb命令.doc
  5. Elasticsearch:理解 Elasticsearch Percolate 查询
  6. 《剑指Offer》-- 树的子结构(Python)
  7. 【Android TV 开发】-->Leanback 中的 HorizontalGridView
  8. CSS 实现加载动画(最简单实现)
  9. win7虚拟串口服务器软件,Virtual Serial Port Driver Pro(虚拟串口驱动程序) V9.0.270 官方版...
  10. html5中链接去除下划线,html超链接去掉下划线 html去除取消超链接下划线