ARM GIC简介与Linux中断处理分析
先简单说明一下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 ¤t_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中断处理分析相关推荐
- ARM Linux中断机制分析
ARM Linux中断机制分析 --以用户模式产生irq中断为例 以下代码基于内核linux2.6.38.3(trimslice官网下载) 本文主要分析ARM发生中断时的处理流程,以在usr态发生 ...
- Linux 中断管理之ARM GIC V3 初始化
1.ARM GIC V3中断控制器介绍 GIC(Generic Interrupt Controller)是一个通用的中断控制器,用来接收硬件中断信号,并经过一定处理后,分发给对应的CPU进行处理.G ...
- Linux 0.11 内核解析:中断相关(1)asm.s文件中断处理分析
0 源代码 有两个版本的,一个是带中文注释,Intel格式的:一个是不带注释是AT&T格式的. Linux 0.11 中文注释版 Linux 0.11 源码,基于<Linux内核完全注释 ...
- arm linux 中断 分析,armlinux中断异常的处理分析.pdf
基于 ARM Linux 中断.异常的处理分析 本文是基于ARM S3C2410X 系统的Linux 2.6 中断.异常和系统调用的处理分析. 主要有以下几个部分: 1. ARM 的硬件中断机制 2. ...
- linux中断处理模式,Linux在保护模式下的中断处理分析.pdf
Linux在保护模式下的中断处理分析.pdf Linux 在保护模式下的中断处理分析 刘万里 杨 斌 (西南交通大学计算机与通信工程学院,成都 610031) E-mail:awan@ 摘 要 该文以 ...
- linux内核分析 网络九,“Linux内核分析”实验报告(九)
一 Linux内核分析博客简介及其索引 本次实验简单的分析了计算机如何进行工作,并通过简单的汇编实例进行解释分析 在本次实验中 通过听老师的视频分析,和自己的学习,初步了解了进程切换的原理.操作系统通 ...
- linux中断处理汇编入口,Linux中断处理体系结构分析(一)
中断也是一种异常,之所以把它单独的列出来,是因为中断的处理与具体的开发板密切相关,除一些必须.共用的中断(比如系统时钟中断.片内外设UART中断)外,必须由驱动开发者提供处理函数.内核提炼出中断处理的 ...
- 期末总结20135320赵瀚青LINUX内核分析与设计期末总结
赵瀚青原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 对LINUX内核分析与设计这 ...
- linux 内核 课程,Linux内核分析课程-全面剖析Linux内核技术 揭开Linux内核的面纱 Linux内核学习视频教 ......
课程名称 Linux内核分析课程-全面剖析Linux内核技术 揭开Linux内核的面纱 Linux内核学习视频 课程目录 (1)\1, 计算机是如何工作的?:目录中文件数:0个 (2)\2, 操作系统 ...
最新文章
- Beta 冲刺 (1/7)
- 前列腺癌检测 AI 算法登上《柳叶刀》:分类性能超过人类专家,还能完成其他临床任务...
- python使用笔记:if __name__ == ‘__main__‘ 如何理解
- 2021-12-27
- 清华大学人工智能研究院成立基础理论研究中心
- Gitlab上传代码
- Django从理论到实战(part44)--JsonResponse类
- std::vector中resize()和reserve()区别
- “以图搜图”的奇葩用途 | 深度
- Python300篇电子书
- c99变长数组_第九章 C99可变长数组VLA详解
- 乐优商城(15)--订单服务
- ffmpeg php 使用教程_php的ffmpeg - CSDN博客
- Bootstrap文字排版
- NAND Flash(spi nand flash和nand flash)和emmc以及ufs通过uboot烧写固件的一些差异
- NOI 4.3 1538: Gopher II(匈牙利算法求最大匹配)
- 根据经度纬度获取距离(km/m)等工具类
- FFmpeg m3u8文件返回Invalid data found when processing input错误
- 8核插上4G翅膀 MT6595借Cortex-A17领跑
- ROS Navigation Stack安装
热门文章
- 数据库修复Part1:创建自己的测试corrupt数据库
- 猛增 174K Star!前端最流行的 10 大顶级开源项目!
- 2020 操作系统第三次习题
- 妙用 background 实现花式文字效果
- yaml语法--多行字符串可以使用|保留换行符,也可以使用>折叠换行
- Istio入门:架构原理及在k8s部署
- 【收藏】ArcGIS 10.8 for Desktop 完整安装教程(含win7/8/10 32/64位+下载地址+亲测可用+汉化)
- 【网址收藏】windows安装Docker Desktop常见问题整理
- k8s基础概念:pause容器和pod控制器类型
- Python3转义字符