历史上的今天
返回首页

历史上的今天

今天是:2025年04月22日(星期二)

正在发生

2020年04月22日 | Linux kernel的中断子系统之(六):ARM中断处理过程

2020-04-22 来源:eefocus

一、前言


本文主要以ARM体系结构下的中断处理为例,讲述整个中断处理过程中的硬件行为和软件动作。具体整个处理过程分成三个步骤来描述:


1、第二章描述了中断处理的准备过程


2、第三章描述了当发生中的时候,ARM硬件的行为


3、第四章描述了ARM的中断进入过程


4、第五章描述了ARM的中断退出过程


本文涉及的代码来自3.14内核。另外,本文注意描述ARM指令集的内容,有些source code为了简短一些,删除了THUMB相关的代码,除此之外,有些debug相关的内容也会删除。


二、中断处理的准备过程


1、中断模式的stack准备


ARM处理器有多种processor mode,例如user mode(用户空间的AP所处于的模式)、supervisor mode(即SVC mode,大部分的内核态代码都处于这种mode)、IRQ mode(发生中断后,处理器会切入到该mode)等。对于linux kernel,其中断处理处理过程中,ARM 处理器大部分都是处于SVC mode。但是,实际上产生中断的时候,ARM处理器实际上是进入IRQ mode,因此在进入真正的IRQ异常处理之前会有一小段IRQ mode的操作,之后会进入SVC mode进行真正的IRQ异常处理。由于IRQ mode只是一个过度,因此IRQ mode的栈很小,只有12个字节,具体如下:


struct stack {

    u32 irq[3];

    u32 abt[3];

    u32 und[3];

} ____cacheline_aligned;


static struct stack stacks[NR_CPUS];


除了irq mode,linux kernel在处理abt mode(当发生data abort exception或者prefetch abort exception的时候进入的模式)和und mode(处理器遇到一个未定义的指令的时候进入的异常模式)的时候也是采用了相同的策略。也就是经过一个简短的abt或者und mode之后,stack切换到svc mode的栈上,这个栈就是发生异常那个时间点current thread的内核栈。anyway,在irq mode和svc mode之间总是需要一个stack保存数据,这就是中断模式的stack,系统初始化的时候,cpu_init函数中会进行中断模式stack的设定:


void notrace cpu_init(void)

{


    unsigned int cpu = smp_processor_id();------获取CPU ID

    struct stack *stk = &stacks[cpu];---------获取该CPU对于的irq abt和und的stack指针


……


#ifdef CONFIG_THUMB2_KERNEL

#define PLC    "r"------Thumb-2下,msr指令不允许使用立即数,只能使用寄存器。

#else

#define PLC    "I"

#endif


    __asm__ (

    "msr    cpsr_c, %1nt"------让CPU进入IRQ mode

    "add    r14, %0, %2nt"------r14寄存器保存stk->irq

    "mov    sp, r14nt"--------设定IRQ mode的stack为stk->irq

    "msr    cpsr_c, %3nt"

    "add    r14, %0, %4nt"

    "mov    sp, r14nt"--------设定abt mode的stack为stk->abt

    "msr    cpsr_c, %5nt"

    "add    r14, %0, %6nt"

    "mov    sp, r14nt"--------设定und mode的stack为stk->und

    "msr    cpsr_c, %7"--------回到SVC mode

        :--------------------上面是code,下面的output部分是空的

        : "r" (stk),----------------------对应上面代码中的%0

          PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),------对应上面代码中的%1

          "I" (offsetof(struct stack, irq[0])),------------对应上面代码中的%2

          PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),------以此类推,下面不赘述

          "I" (offsetof(struct stack, abt[0])),

          PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE),

          "I" (offsetof(struct stack, und[0])),

          PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE)

        : "r14");--------上面是input操作数列表,r14是要clobbered register列表

}


嵌入式汇编的语法格式是:asm(code : output operand list : input operand list : clobber list);大家对着上面的code就可以分开各段内容了。在input operand list中,有两种限制符(constraint),"r"或者"I","I"表示立即数(Immediate operands),"r"表示用通用寄存器传递参数。clobber list中有一个r14,表示在汇编代码中修改了r14的值,这些信息是编译器需要的内容。


对于SMP,bootstrap CPU会在系统初始化的时候执行cpu_init函数,进行本CPU的irq、abt和und三种模式的内核栈的设定,具体调用序列是:start_kernel--->setup_arch--->setup_processor--->cpu_init。对于系统中其他的CPU,bootstrap CPU会在系统初始化的最后,对每一个online的CPU进行初始化,具体的调用序列是:start_kernel--->rest_init--->kernel_init--->kernel_init_freeable--->kernel_init_freeable--->smp_init--->cpu_up--->_cpu_up--->__cpu_up。__cpu_up函数是和CPU architecture相关的。对于ARM,其调用序列是__cpu_up--->boot_secondary--->smp_ops.smp_boot_secondary(SOC相关代码)--->secondary_startup--->__secondary_switched--->secondary_start_kernel--->cpu_init。


除了初始化,系统电源管理也需要irq、abt和und stack的设定。如果我们设定的电源管理状态在进入sleep的时候,CPU会丢失irq、abt和und stack point寄存器的值,那么在CPU resume的过程中,要调用cpu_init来重新设定这些值。


2、SVC模式的stack准备


我们经常说进程的用户空间和内核空间,对于一个应用程序而言,可以运行在用户空间,也可以通过系统调用进入内核空间。在用户空间,使用的是用户栈,也就是我们软件工程师编写用户空间程序的时候,保存局部变量的stack。陷入内核后,当然不能用用户栈了,这时候就需要使用到内核栈。所谓内核栈其实就是处于SVC mode时候使用的栈。


在linux最开始启动的时候,系统只有一个进程(更准确的说是kernel thread),就是PID等于0的那个进程,叫做swapper进程(或者叫做idle进程)。该进程的内核栈是静态定义的,如下:


union thread_union init_thread_union __init_task_data =

    { INIT_THREAD_INFO(init_task) };


union thread_union {

    struct thread_info thread_info;

    unsigned long stack[THREAD_SIZE/sizeof(long)];

};


对于ARM平台,THREAD_SIZE是8192个byte,因此占据两个page frame。随着初始化的进行,Linux kernel会创建若干的内核线程,而在进入用户空间后,user space的进程也会创建进程或者线程。Linux kernel在创建进程(包括用户进程和内核线程)的时候都会分配一个(或者两个,和配置相关)page frame,具体代码如下:


static struct task_struct *dup_task_struct(struct task_struct *orig)

{

    ......


    ti = alloc_thread_info_node(tsk, node);

    if (!ti)

        goto free_tsk;


    ......

}


底部是struct thread_info数据结构,顶部(高地址)就是该进程的内核栈。当进程切换的时候,整个硬件和软件的上下文都会进行切换,这里就包括了svc mode的sp寄存器的值被切换到调度算法选定的新的进程的内核栈上来。

 

3、异常向量表的准备


对于ARM处理器而言,当发生异常的时候,处理器会暂停当前指令的执行,保存现场,转而去执行对应的异常向量处的指令,当处理完该异常的时候,恢复现场,回到原来的那点去继续执行程序。系统所有的异常向量(共计8个)组成了异常向量表。向量表(vector table)的代码如下:


.section .vectors, "ax", %progbits

__vectors_start:

    W(b)    vector_rst

    W(b)    vector_und

    W(ldr)    pc, __vectors_start + 0x1000

    W(b)    vector_pabt

    W(b)    vector_dabt

    W(b)    vector_addrexcptn

    W(b)    vector_irq ---------------------------IRQ Vector

    W(b)    vector_fiq


对于本文而言,我们重点关注vector_irq这个exception vector。异常向量表可能被安放在两个位置上:


(1)异常向量表位于0x0的地址。这种设置叫做Normal vectors或者Low vectors。


(2)异常向量表位于0xffff0000的地址。这种设置叫做high vectors


具体是low vectors还是high vectors是由ARM的一个叫做的SCTLR寄存器的第13个bit (vector bit)控制的。对于启用MMU的ARM Linux而言,系统使用了high vectors。为什么不用low vector呢?对于linux而言,0~3G的空间是用户空间,如果使用low vector,那么异常向量表在0地址,那么则是用户空间的位置,因此linux选用high vector。当然,使用Low vector也可以,这样Low vector所在的空间则属于kernel space了(也就是说,3G~4G的空间加上Low vector所占的空间属于kernel space),不过这时候要注意一点,因为所有的进程共享kernel space,而用户空间的程序经常会发生空指针访问,这时候,内存保护机制应该可以捕获这种错误(大部分的MMU都可以做到,例如:禁止userspace访问kernel space的地址空间),防止vector table被访问到。对于内核中由于程序错误导致的空指针访问,内存保护机制也需要控制vector table被修改,因此vector table所在的空间被设置成read only的。在使用了MMU之后,具体异常向量表放在那个物理地址已经不重要了,重要的是把它映射到0xffff0000的虚拟地址就OK了,具体代码如下:


static void __init devicemaps_init(const struct machine_desc *mdesc)

{

    ……

    vectors = early_alloc(PAGE_SIZE * 2); -----分配两个page的物理页帧


    early_trap_init(vectors); -------copy向量表以及相关help function到该区域


    ……

    map.pfn = __phys_to_pfn(virt_to_phys(vectors));

    map.virtual = 0xffff0000;

    map.length = PAGE_SIZE;

#ifdef CONFIG_KUSER_HELPERS

    map.type = MT_HIGH_VECTORS;

#else

    map.type = MT_LOW_VECTORS;

#endif

    create_mapping(&map); ----------映射0xffff0000的那个page frame


    if (!vectors_high()) {---如果SCTLR.V的值设定为low vectors,那么还要映射0地址开始的memory

        map.virtual = 0;

        map.length = PAGE_SIZE * 2;

        map.type = MT_LOW_VECTORS;

        create_mapping(&map);

    }


    map.pfn += 1;

    map.virtual = 0xffff0000 + PAGE_SIZE;

    map.length = PAGE_SIZE;

    map.type = MT_LOW_VECTORS;

    create_mapping(&map); ----------映射high vecotr开始的第二个page frame


……

}


为什么要分配两个page frame呢?这里vectors table和kuser helper函数(内核空间提供的函数,但是用户空间使用)占用了一个page frame,另外异常处理的stub函数占用了另外一个page frame。为什么会有stub函数呢?稍后会讲到。


在early_trap_init函数中会初始化异常向量表,具体代码如下:


void __init early_trap_init(void *vectors_base)

{

    unsigned long vectors = (unsigned long)vectors_base;

    extern char __stubs_start[], __stubs_end[];

    extern char __vectors_start[], __vectors_end[];

    unsigned i;


    vectors_page = vectors_base;


    将整个vector table那个page frame填充成未定义的指令。起始vector table加上kuser helper函数并不能完全的充满这个page,有些缝隙。如果不这么处理,当极端情况下(程序错误或者HW的issue),CPU可能从这些缝隙中取指执行,从而导致不可知的后果。如果将这些缝隙填充未定义指令,那么CPU可以捕获这种异常。

    for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)

        ((u32 *)vectors_base)[i] = 0xe7fddef1;


  拷贝vector table,拷贝stub function

    memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);

    memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);


    kuser_init(vectors_base); ----copy kuser helper function


    flush_icache_range(vectors, vectors + PAGE_SIZE * 2);

    modify_domain(DOMAIN_USER, DOMAIN_CLIENT);


}


一旦涉及代码的拷贝,我们就需要关心其编译连接时地址(link-time address)和运行时地址(run-time address)。在kernel完成链接后,__vectors_start有了其link-time address,如果link-time address和run-time address一致,那么这段代码运行时毫无压力。但是,目前对于vector table而言,其被copy到其他的地址上(对于High vector,这是地址就是0xffff00000),也就是说,link-time address和run-time address不一样了,如果仍然想要这些代码可以正确运行,那么需要这些代码是位置无关的代码。对于vector table而言,必须要位置无关。B这个branch instruction本身就是位置无关的,它可以跳转到一个当前位置的offset。不过并非所有的vector都是使用了branch instruction,对于软中断,其vector地址上指令是“W(ldr)    pc, __vectors_start + 0x1000 ”,这条指令被编译器编译成ldr     pc, [pc, #4080],这种情况下,该指令也是位置无关的,但是有个限制,offset必须在4K的范围内,这也是为何存在stub section的原因了。


4、中断控制器的初始化


具体可以参考GIC代码分析。


三、ARM HW对中断事件的处理


当一切准备好之后,一旦打开处理器的全局中断就可以处理来自外设的各种中断事件了。


当外设(SOC内部或者外部都可以)检测到了中断事件,就会通过interrupt requestion line上的电平或者边沿(上升沿或者下降沿或者both)通知到该外设连接到的那个中断控制器,而中断控制器就会在多个处理器中选择一个,并把该中断通过IRQ(或者FIQ,本文不讨论FIQ的情况)分发给该processor。ARM处理器感知到了中断事件后,会进行下面一系列的动作:


1、修改CPSR(Current Program Status Register)寄存器中的M[4:0]。M[4:0]表示了ARM处理器当前处于的模式( processor modes)。ARM定义的mode包括:

image.png?imageView2/2/w/550

一旦设定了CPSR.M,ARM处理器就会将processor mode切换到IRQ mode。


2、保存发生中断那一点的CPSR值(step 1之前的状态)和PC值

image.png?imageView2/2/w/550  

在IRQ mode下,CPU看到的R0~R12寄存器、PC以及CPSR是和usr mode(userspace)或者svc mode(kernel space)是一样的。不同的是IRQ mode下,有自己的R13(SP,stack pointer)、R14(LR,link register)和SPSR(Saved Program Status Register)。


CPSR是共用的,虽然中断可能发生在usr mode(用户空间),也可能是svc mode(内核空间),不过这些信息都是体现在CPSR寄存器中。硬件会将发生中断那一刻的CPSR保存在SPSR寄存器中(由于不同的mode下有不同的SPSR寄存器,因此更准确的说应该是SPSR-irq,也就是IRQ mode中的SPSR寄存器)。

推荐阅读

史海拾趣

Anadigm公司的发展小趣事

Anadigm是一家曾经存在的半导体公司,专注于可编程模拟信号处理器(PASP)技术。以下是Anadigm公司发展的相关故事:

  1. 公司成立与初期发展:Anadigm公司成立于1997年,总部位于美国加利福尼亚州圣塔莫尼卡市。公司的创始人致力于开发一种新型的可编程模拟信号处理器(PASP),以应对传统模拟电路设计的局限性。通过引入数字技术,Anadigm旨在提供更灵活、高性能的模拟信号处理解决方案。

  2. PASP技术的推出与市场应用:Anadigm公司于2000年推出了其首款可编程模拟信号处理器产品系列。这些器件具有灵活的可编程性和高度集成的特点,能够适应多种应用场景,包括电力管理、医疗设备、汽车电子和工业控制等领域。Anadigm的PASP技术受到了行业的关注,并在市场上取得了一定的成功。

  3. 技术创新与产品优化:Anadigm公司不断投入研发,致力于改进其PASP技术并推出更先进的产品。公司持续与客户合作,了解市场需求并进行技术创新,以满足不断变化的行业需求。Anadigm的产品不断优化,性能不断提升,赢得了客户的信赖和市场份额的扩大。

  4. 合并与收购:尽管Anadigm公司在PASP技术方面取得了一定的成就,但面临着激烈的市场竞争和资金压力。在公司运营一段时间后,Anadigm于2008年被美国半导体公司Exar Corporation收购。此次收购使得Anadigm成为Exar的全资子公司,继续在模拟信号处理领域发展。

  5. 最终终止业务:然而,随着时间的推移,Anadigm在市场上的地位逐渐下滑,未能在激烈的竞争中保持竞争优势。最终,Exar Corporation于2014年宣布终止Anadigm的业务,并关闭其产品线。这标志着Anadigm作为一个独立的实体在半导体行业的终结。

以上是Anadigm公司发展的一些主要故事,展示了该公司从创立到终止业务的发展历程。

Galaxy Microelectronics公司的发展小趣事

深圳市飞翼科技有限公司自2006年成立以来,一直致力于模拟与数字MCU混合芯片领域的研究、设计和开发应用。公司主攻电容式触摸感应按键芯片设计,凭借多项独有的专利技术,成功突破了行业内的技术难点。经过多年的努力,飞翼科技已成为该应用领域中技术最全面、市场份额最大的公司之一。其电容式触摸感应芯片广泛应用于各类电子产品中,为用户带来了更加便捷、智能的交互体验。

American Micro Products Inc公司的发展小趣事

为了保持技术的领先地位,AMP高度重视研发团队的建设和人才培养。公司投入大量资源用于引进和培养高端人才,为研发团队提供了一流的工作环境和研发设备。这些人才为AMP的技术创新和产品升级提供了强有力的支持。

Arctic Silicon Devices公司的发展小趣事

在电子行业的发展过程中,创新合作是推动产业进步的重要动力。Arctic Silicon Devices积极与高校、科研机构等合作,共同开展技术研发和人才培养。通过共享资源、互通有无,公司不仅获得了更多的创新灵感和技术支持,还推动了整个电子行业的技术进步和产业升级。

H&D Wireless公司的发展小趣事

随着全球化进程的加速,高创也开始了其全球化布局的步伐。除了在以色列和中国设立研发中心外,高创还积极拓展国际市场,与全球多个国家和地区的客户建立了长期合作关系。通过参加国际知名展会如汉诺威工业博览会等,高创不仅展示了其最新技术和产品,还加强了与国际同行的交流与合作,进一步提升了其国际知名度和影响力。

Exclara Inc公司的发展小趣事

随着技术的不断成熟和产品的日益完善,Exclara开始将目光投向全球市场。公司积极拓展海外市场,通过参加国际展会、与当地合作伙伴建立战略合作关系等方式,不断提升品牌知名度和市场份额。同时,公司也在全球范围内建立了完善的销售网络和售后服务体系,以确保客户能够享受到优质的产品和服务。

问答坊 | AI 解惑

去做人生的那棵树,关于工程师命运的感慨。

去做人生的那棵树,关于工程师命运的感慨。 做为一个在嵌入式系统行业从业十年的工程师来说,为了生存与发展,现在也还一天天忙于奔命,连以前最常来的这个BBS论坛也有近一个多月没来,以下有些想法希望能与大家分享。希望能给刚入道的工程师或已经 ...…

查看全部问答>

linux 视频教程下载--推荐

天嵌科技继推出Linux2.6.25完整移植教程之后,再推出TQ2440精品视频教程, 使你更容易上手,更快捷入门,更早进入嵌入式开发的殿堂。 欢迎大家到http://www.embedsky.net/technical/homePage.html 下载, 到http://www.embedsky.net/bbs 开题讨 ...…

查看全部问答>

基于DSP的手持式数字存储示波表系统设计

基于DSP的手持式数字存储示波表系统设计…

查看全部问答>

利用SOPC Builder生成系统时的问题

利用SOPC Builder生成系统时,需要添加一个SSRAM模块进去,但是QUARTUS II 7.2版本的SSRAM型号是CY7C1380C,而我的板子上面用到的是IS61LPS12836A_200TQLI,我查看了二者的datasheet,发现二者引脚完全一致,以及真值表也一样,请问能否用CY7C1380C ...…

查看全部问答>

基于STC89C51+M62429的音量控制电路与程序设计

1  引言 本文介绍的音量控制IC为M62429,市面上有很多类似的产品,例如:FM62429、CD62429、CSC62429等。其实,掌握了一两种IC的编程方法,稍作改动就很容易移植到其它产品上。M62429是日本三菱公司的音量控制IC,音量调节范围是0~-83 ...…

查看全部问答>

问个关于IIC的问题

在wince 6.0下,相同的代码,用在两个环境下,s3c2442x的芯片 在一个环境下正常,在另一个环境下: IIC数据发送出去后,从设备不返回完成标志是什么原因?就是说CON寄存器的第5位没有被置一。 哪位大侠碰到过,指点下,不胜感激~…

查看全部问答>

sd卡驱动

在SD卡驱动程序中有这样一个定义:WCHAR *szRegKey_SDMMC = L\"Drivers\\\\SDCARD\\\\ClientDrivers\\\\Class\\\\SDMemory_Class\"; 在中断线程中,通过这条语句调用szRegKey_SDMMC: hDevice = ActivateDeviceEx(szRegKey_SDMMC, NULL, 0, NULL ...…

查看全部问答>

急!如何编程配置 PIC16F单片机FOSC1 FOSC2位使其工作在HS模式?

如何编程配置 PIC16F单片机FOSC1 FOSC2位使其工作在HS模式?…

查看全部问答>

请教单片机中一个宏定义

在一个51中有这样一个关于地址的宏定义,是cc2430里面的51: #define XREG  (addr)   ((unsigned char volatile __xdata  *) 0 )   [addr] 请问这个宏定义什么意思? 上面的0又是什么意思呢?这是 ...…

查看全部问答>

WIN CE中的蓝牙问题!请大师门指教

在CE中有蓝牙DLL文件,我想复制一份到其他CE中,请问有什么办法,或者这些DLL(蓝牙)文件,哪里可以下载到,谢谢!…

查看全部问答>