先简单说明一下GIC(具体详尽的介绍请查阅ARM GIC相关文档)

GIC即general interrupt controller。

它是一个架构,版本历经了GICv1(已弃用),GICv2,GICv3,GICv4。对于不同的GIC版本,arm公司设计了对应的GIC IP

GIC的核心功能:对soc中外设的中断源的管理,并且提供给软件,配置以及控制这些中断源。

下面一张ARM GICv2 的图

中断源类型说明:

SGI(Software-generated interrupt):范围0 - 15,软件触发的中断,一般用于核间通讯

PPI(Private peripheral interrupt ): 范围16 - 31,私有外设中断,只对指定的core有效

SPI(Shared peripheral interrupt):范围32 - 1019,共享中断,不限定特定的core

控制分两部分,Distributor和CPU interface

Distributor对中断的控制包括:
(1)中断enable或者disable的控制
(2)将当前优先级最高的中断事件分发到一个或者一组CPU interface
(3)优先级控制
(4)interrupt属性设定。例如是level-sensitive还是edge-triggered
(5)interrupt group的设定

CPU interface:将GICD发送的中断信息,通过IRQ,FIQ管脚,传输给core

GIC对中断的处理包括以下4种状态:

  • inactive:中断处于无效状态

  • pending:中断处于有效状态,但是cpu没有响应该中断

  • active:cpu在响应该中断

  • active and pending:cpu在响应该中断,但是该中断源又发送中断过来

其转换过程如下:

发生中断信号走向:

至此,关于GIC相关的介绍基本可以满足阅读Linux内核关于中断处理的相关code

以下源代码基于ssd20x官方kernel为例,cortexA7,ARMv7架构,32位

中断向量入口位于:arch/arm/kernel/entry-armv.S

/** Interrupt handling.*/.macro  irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLERldr  r1, =handle_arch_irqmov    r0, spbadr  lr, 9997fldr    pc, [r1]
#elsearch_irq_handler_default
#endif

关于宏CONFIG_MULTI_IRQ_HANDLER,表示"允许每台机器在运行时指定它自己的IRQ处理程序",因为kernel一般支持多种平台,多种处理器,所以此选项一般开启

进入irq_handler有两种情况

一个是用户模式下发生了中断

 .align  5
__irq_usr:usr_entrykuser_cmpxchg_checkirq_handlerget_thread_info tskmov why, #0b    ret_to_user_from_irqUNWIND(.fnend       )
ENDPROC(__irq_usr)

另一个是内核态时发生了中断

 .align  5
__irq_svc:svc_entryirq_handler#ifdef CONFIG_PREEMPTldr  r8, [tsk, #TI_PREEMPT]      @ get preempt countldr r0, [tsk, #TI_FLAGS]        @ get flagsteq r8, #0              @ if preempt count != 0movne  r0, #0              @ force flags to 0tst  r0, #_TIF_NEED_RESCHEDblne  svc_preempt
#endifsvc_exit r5, irq = 1         @ return from exceptionUNWIND(.fnend       )
ENDPROC(__irq_svc)

在irq_handler中handle_arch_irq即为动态指定的中断处理函数

位于arch/arm/kernel/irq.c

#ifdef CONFIG_MULTI_IRQ_HANDLER
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{if (handle_arch_irq)return;handle_arch_irq = handle_irq;
}
#endif

在gic初始化drivers/irqchip/irq-gic.c过程进行设置set_handle_irq(gic_handle_irq);

static int __init __gic_init_bases(struct gic_chip_data *gic,int irq_start,struct fwnode_handle *handle)
{char *name;int i, ret;if (WARN_ON(!gic || gic->domain))return -EINVAL;if (gic == &gic_data[0]) {/** Initialize the CPU interface map to all CPUs.* It will be refined as each CPU probes its ID.* This is only necessary for the primary GIC.*/for (i = 0; i < NR_GIC_CPU_IF; i++)gic_cpu_map[i] = 0xff;
#ifdef CONFIG_SMPset_smp_cross_call(gic_raise_softirq);
#endifcpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,"AP_IRQ_GIC_STARTING",gic_starting_cpu, NULL);-----初始化过程中注册具体的处理函数set_handle_irq(gic_handle_irq);if (static_key_true(&supports_deactivate))pr_info("GIC: Using split EOI/Deactivate mode\n");}if (static_key_true(&supports_deactivate) && gic == &gic_data[0]) {name = kasprintf(GFP_KERNEL, "GICv2");gic_init_chip(gic, NULL, name, true);} else {name = kasprintf(GFP_KERNEL, "GIC-%d", (int)(gic-&gic_data[0]));gic_init_chip(gic, NULL, name, false);}ret = gic_init_bases(gic, irq_start, handle);if (ret)kfree(name);return ret;
}

函数gic_handle_irq即为具体的中断处理函数,此时中断号是被GIC屏蔽的

static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
    u32 irqstat, irqnr;
    struct gic_chip_data *gic = &gic_data[0];
    void __iomem *cpu_base = gic_data_cpu_base(gic);

do {

-----从寄存器读取硬件中断号
        irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
        irqnr = irqstat & GICC_IAR_INT_ID_MASK;
        
#if defined(CONFIG_MP_IRQ_TRACE)
             ms_records_irq_count(irqnr);
#endif

-----处理PPI 及 SPI中断
        if (likely(irqnr > 15 && irqnr < 1020)) {
            if (static_key_true(&supports_deactivate))
                writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
            handle_domain_irq(gic->domain, irqnr, regs);
            continue;
        }

------处理SGI中断
        if (irqnr < 16) {
            writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
            if (static_key_true(&supports_deactivate))
                writel_relaxed(irqstat, cpu_base + GIC_CPU_DEACTIVATE);
#ifdef CONFIG_SMP
            /*
             * Ensure any shared data written by the CPU sending
             * the IPI is read after we've read the ACK register
             * on the GIC.
             *
             * Pairs with the write barrier in gic_raise_softirq
             */

-----内存屏障
            smp_rmb();

-----进入处理
            handle_IPI(irqnr, regs);
。。。。。
            continue;
        }
        break;
    } while (1);
}

handle_domain_irq的实现如下:

#ifdef CONFIG_HANDLE_DOMAIN_IRQ
/**
 * __handle_domain_irq - Invoke the handler for a HW irq belonging to a domain
 * @domain:    The domain where to perform the lookup
 * @hwirq:    The HW irq number to convert to a logical one
 * @lookup:    Whether to perform the domain lookup or not
 * @regs:    Register file coming from the low-level handling code
 *
 * Returns:    0 on success, or -EINVAL if conversion has failed
 */
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
            bool lookup, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    unsigned int irq = hwirq;
    int ret = 0;

--------关闭抢占

    irq_enter();

#ifdef CONFIG_IRQ_DOMAIN
    if (lookup)
        irq = irq_find_mapping(domain, hwirq);
#endif

/*
     * Some hardware gives randomly wrong interrupts.  Rather
     * than crashing, do something sensible.
     */
    if (unlikely(!irq || irq >= nr_irqs)) {
        ack_bad_irq(irq);
        ret = -EINVAL;
    } else {

------执行处理
        generic_handle_irq(irq);  ----实际为注册IRQ号对应的函数:desc->handle_irq(desc);       
    }

----关闭抢占以及检测是否有软中断需要处理

irq_exit();
    set_irq_regs(old_regs);
    return ret;
}
#endif

关于抢占,即在适当的时机及时调用更高优先级的任务;

几个抢占点:

从中断返回到内核空间时;解锁或使能软中断时;调用 preempt_enable 使能抢占;

显式调度;任务阻塞时;

不可抢占点:

正处于中断中;持有锁时;正在调度时;对Per-CPU操作时

抢占的具体实现为对一个preempt_count变量进行互斥操作,位于thread_info结构中

static __always_inline volatile int *preempt_count_ptr(void)

{

return &current_thread_info()->preempt_count;

}

static __always_inline void __preempt_count_add(int val)

{

*preempt_count_ptr() += val;

}

static __always_inline void __preempt_count_sub(int val)

{

*preempt_count_ptr() -= val;

}

#define preempt_count_add(val) __preempt_count_add(val)

#define preempt_count_sub(val) __preempt_count_sub(val)

#define preempt_count_dec_and_test() __preempt_count_dec_and_test()

#define preempt_count_inc() preempt_count_add(1)

#define preempt_count_dec() preempt_count_sub(1)

借用一张图详细描述

关于软中断,中断底半部的一种实现方式,静态分配,一共10种,可以充分利用SMP性能;

其执行点一般在irq_exit时或者独立的内核线程中

在irq_exit中会进行有没有待处理软中断的检测(其实就是判断preempt_count的BIT8-15)

/*

* Exit an interrupt context. Process softirqs if needed and possible:

*/

void irq_exit(void)

{

#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED

local_irq_disable();

#else

lockdep_assert_irqs_disabled();

#endif

account_irq_exit_time(current);

-----计数-1

preempt_count_sub(HARDIRQ_OFFSET);

----不在中断中以及有待处理软中断

if (!in_interrupt() && local_softirq_pending())

invoke_softirq();  ----实际为__do_softirq或者wakeup_softirqd

tick_irq_exit();

rcu_irq_exit();

trace_hardirq_exit(); /* must be last! */

}

asmlinkage __visible void __softirq_entry __do_softirq(void)

{

unsigned long end = jiffies + MAX_SOFTIRQ_TIME;

unsigned long old_flags = current->flags;

int max_restart = MAX_SOFTIRQ_RESTART;

struct softirq_action *h;

bool in_hardirq;

__u32 pending;

int softirq_bit;

#if defined(CONFIG_MP_IRQ_TRACE)

MSYS_IRQ_INFO irq_info;

#endif

/*

* Mask out PF_MEMALLOC s current task context is borrowed for the

* softirq. A softirq handled such as network RX might set PF_MEMALLOC

* again if the socket is related to swap

*/

current->flags &= ~PF_MEMALLOC;

pending = local_softirq_pending();

account_irq_enter_time(current);

-----软中断不可嵌套

__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);

in_hardirq = lockdep_softirq_start();

restart:

/* Reset the pending bitmask before enabling irqs */

set_softirq_pending(0);

----开启中断,此后的部分随时会被中断打断;因此要保证软中短处理好函数的可重入性

local_irq_enable();

h = softirq_vec;

while ((softirq_bit = ffs(pending))) {

unsigned int vec_nr;

int prev_count;

h += softirq_bit - 1;

vec_nr = h - softirq_vec;

prev_count = preempt_count();

kstat_incr_softirqs_this_cpu(vec_nr);

#if defined(CONFIG_MP_IRQ_TRACE)

irq_info.IRQNumber = vec_nr;

irq_info.action = h->action;

irq_info.timeStart = sched_clock();

#endif

trace_softirq_entry(vec_nr);

---执行处理函数

h->action(h);

trace_softirq_exit(vec_nr);

#if defined(CONFIG_MP_IRQ_TRACE)

irq_info.timeEnd = sched_clock();

if (irq_info.timeEnd - irq_info.timeStart > 2500000)

{

if(sirq_head_initialized)

{

ms_records_sirq(&irq_info);

}

}

#endif

if (unlikely(prev_count != preempt_count())) {

pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",

vec_nr, softirq_to_name[vec_nr], h->action,

prev_count, preempt_count());

preempt_count_set(prev_count);

}

h++;

pending >>= softirq_bit;

}

rcu_bh_qs();

----处理完后关闭中断

local_irq_disable();

----再次检测是否有待处理软中断(因为软中断处理过程中开启了中断)

pending = local_softirq_pending();

if (pending) {

----软中断有最大执行时间限制

if (time_before(jiffies, end) && !need_resched() &&

--max_restart)

---有的话继续执行软中断

goto restart;

----软中断积累太多,调用独立的内核线程ksoftirqd执行,每个core都有一个ksoftirqd,其实际上是一个Per-CPU变量

wakeup_softirqd();

}

lockdep_softirq_end(in_hardirq);

account_irq_exit_time(current);

-----软中断底半部使能

__local_bh_enable(SOFTIRQ_OFFSET);

WARN_ON_ONCE(in_interrupt());

tsk_restore_flags(current, old_flags, PF_MEMALLOC);

}

总结:

1. GIC分为两部分,Distributor(处理优先级,中断保持,信号分发等)和CPU interface(响应及完成中断等)

2. Linux中断不可嵌套

3. Linux中断处理分为上半部(快速处理和响应)和底半部(延迟执行)

4. 底半部常用手段有软中断(可以在不同CPU并发),tasklet(同一个tasklet在同一个CPU排队执行)以及工作队列(可睡眠)

5. 关于Per-CPU变量,编译时位于特定的section中,加载内存后,每个cpu都有一份拷贝,各自用各自的独立拷贝,不存在多cpu访问互斥问题,只有本cpu线程和本cpu中断互斥访问问题

6. 关于内存屏障,因为cpu大多都是流水线工作方式,并且可以乱序执行,内存屏障保证语句前后的指令具备严格的先后执行顺序

ARM GIC简介与Linux中断处理分析相关推荐

  1. ARM Linux中断机制分析

      ARM Linux中断机制分析 --以用户模式产生irq中断为例 以下代码基于内核linux2.6.38.3(trimslice官网下载) 本文主要分析ARM发生中断时的处理流程,以在usr态发生 ...

  2. Linux 中断管理之ARM GIC V3 初始化

    1.ARM GIC V3中断控制器介绍 GIC(Generic Interrupt Controller)是一个通用的中断控制器,用来接收硬件中断信号,并经过一定处理后,分发给对应的CPU进行处理.G ...

  3. Linux 0.11 内核解析:中断相关(1)asm.s文件中断处理分析

    0 源代码 有两个版本的,一个是带中文注释,Intel格式的:一个是不带注释是AT&T格式的. Linux 0.11 中文注释版 Linux 0.11 源码,基于<Linux内核完全注释 ...

  4. arm linux 中断 分析,armlinux中断异常的处理分析.pdf

    基于 ARM Linux 中断.异常的处理分析 本文是基于ARM S3C2410X 系统的Linux 2.6 中断.异常和系统调用的处理分析. 主要有以下几个部分: 1. ARM 的硬件中断机制 2. ...

  5. linux中断处理模式,Linux在保护模式下的中断处理分析.pdf

    Linux在保护模式下的中断处理分析.pdf Linux 在保护模式下的中断处理分析 刘万里 杨 斌 (西南交通大学计算机与通信工程学院,成都 610031) E-mail:awan@ 摘 要 该文以 ...

  6. linux内核分析 网络九,“Linux内核分析”实验报告(九)

    一 Linux内核分析博客简介及其索引 本次实验简单的分析了计算机如何进行工作,并通过简单的汇编实例进行解释分析 在本次实验中 通过听老师的视频分析,和自己的学习,初步了解了进程切换的原理.操作系统通 ...

  7. linux中断处理汇编入口,Linux中断处理体系结构分析(一)

    中断也是一种异常,之所以把它单独的列出来,是因为中断的处理与具体的开发板密切相关,除一些必须.共用的中断(比如系统时钟中断.片内外设UART中断)外,必须由驱动开发者提供处理函数.内核提炼出中断处理的 ...

  8. 期末总结20135320赵瀚青LINUX内核分析与设计期末总结

    赵瀚青原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 对LINUX内核分析与设计这 ...

  9. linux 内核 课程,Linux内核分析课程-全面剖析Linux内核技术 揭开Linux内核的面纱 Linux内核学习视频教 ......

    课程名称 Linux内核分析课程-全面剖析Linux内核技术 揭开Linux内核的面纱 Linux内核学习视频 课程目录 (1)\1, 计算机是如何工作的?:目录中文件数:0个 (2)\2, 操作系统 ...

最新文章

  1. Beta 冲刺 (1/7)
  2. 前列腺癌检测 AI 算法登上《柳叶刀》:分类性能超过人类专家,还能完成其他临床任务...
  3. python使用笔记:if __name__ == ‘__main__‘ 如何理解
  4. 2021-12-27
  5. 清华大学人工智能研究院成立基础理论研究中心
  6. Gitlab上传代码
  7. Django从理论到实战(part44)--JsonResponse类
  8. std::vector中resize()和reserve()区别
  9. “以图搜图”的奇葩用途 | 深度
  10. Python300篇电子书
  11. c99变长数组_第九章 C99可变长数组VLA详解
  12. 乐优商城(15)--订单服务
  13. ffmpeg php 使用教程_php的ffmpeg - CSDN博客
  14. Bootstrap文字排版
  15. NAND Flash(spi nand flash和nand flash)和emmc以及ufs通过uboot烧写固件的一些差异
  16. NOI 4.3 1538: Gopher II(匈牙利算法求最大匹配)
  17. 根据经度纬度获取距离(km/m)等工具类
  18. FFmpeg m3u8文件返回Invalid data found when processing input错误
  19. 8核插上4G翅膀 MT6595借Cortex-A17领跑
  20. ROS Navigation Stack安装

热门文章

  1. 数据库修复Part1:创建自己的测试corrupt数据库
  2. 猛增 174K Star!前端最流行的 10 大顶级开源项目!
  3. 2020 操作系统第三次习题
  4. 妙用 background 实现花式文字效果
  5. yaml语法--多行字符串可以使用|保留换行符,也可以使用>折叠换行
  6. Istio入门:架构原理及在k8s部署
  7. 【收藏】ArcGIS 10.8 for Desktop 完整安装教程(含win7/8/10 32/64位+下载地址+亲测可用+汉化)
  8. 【网址收藏】windows安装Docker Desktop常见问题整理
  9. k8s基础概念:pause容器和pod控制器类型
  10. Python3转义字符