中断分类
在中断的多种分类方法中,我们根据中断的来源来分类:
内部中断:例如软件中断指令, 溢出,除法错误等。操作系统从用户态切换到内核态。都会在一条指令执行完后才会触发,因为为同步。
外部中断:由外设提供。由于不知道何时中断会到来,因此为异步过程。
中断处理器硬件架构
外部中断的整体框架如下图:
首先我们先了解几个名词:
软件中断irq:它是发生设备中断时处理器从pic中读到的中断号码,在内核建立的中断处理框架内,会使用这个irq号来表示一个外设的中断,并调用对应的中断处理例程。
中断向量表(vector table):除了外部中断还有异常,陷阱等异常事件,中断向量表里面的每一项都是一个中断或异常处理函数的入口地址。外部设备的中断常常对应向量表中的某一项,这是个通用的外部中断处理函数入口,因此进入通用的中断处理函数之后,系统必须要知道正在处理的中断是哪一个设备产生的,而这正是由irq决定的。中断向量表的内容由操作系统在初始化阶段来填写。对于外部中断,操作系统负责实现一个通用的外部中断处理函数,然后把这个函数的入口地址翻到中断向量表中的对应位置。
可编程中断控制器:pic(programmable interrupt controller) ,一般可通过处理器进行编程配置。
下面是从arm开发手册当中gic的中断控制器框架图:
arm gic支持3种类型的中断:
sgi(software generated interrupt): 软件产生的中断,可以用于多核间通信。一个cpu可以通过写gic寄存器给另一个cpu产生中断。多核间调度用的ipi_wakeup, ipi_timer,.....等都是由sgi产生
ppi(private peripheral interrupt): 某个cpu私有外设的中断,这类外设的中断只能发送给绑定的那个cpu
spi(share peripheral interrupt):共享外设中断,这类外设的中断可以路由到任何一个cpu
linux外部中断处理程序框架
外部中断处理程序架构如下:
内核中断向量表初始化过程(下文在不做特别说明的状况下均以linux5.0内核为版本进行分析)
//arch/arm64/kernel/entry.s/**exception vectors*/ .pushsection .entry.text, ax .align 11 entry(vectors) kernel_ventry 1, sync_invalid // synchronous el1t kernel_ventry 1, irq_invalid // irq el1t kernel_ventry 1, fiq_invalid // fiq el1t kernel_ventry 1, error_invalid // error el1t kernel_ventry 1, sync // synchronous el1h kernel_ventry 1, irq // irq el1h kernel_ventry 1, fiq_invalid // fiq el1h kernel_ventry 1, error // error el1h kernel_ventry 0, sync // synchronous 64-bit el0 kernel_ventry 0, irq // irq 64-bit el0 kernel_ventry 0, fiq_invalid // fiq 64-bit el0 kernel_ventry 0, error // error 64-bit el0 #ifdef config_compat kernel_ventry 0, sync_compat, 32 // synchronous 32-bit el0 kernel_ventry 0, irq_compat, 32 // irq 32-bit el0 kernel_ventry 0, fiq_invalid_compat, 32 // fiq 32-bit el0 kernel_ventry 0, error_compat, 32 // error 32-bit el0 #else kernel_ventry 0, sync_invalid, 32 // synchronous 32-bit el0 kernel_ventry 0, irq_invalid, 32 // irq 32-bit el0 kernel_ventry 0, fiq_invalid, 32 // fiq 32-bit el0 kernel_ventry 0, error_invalid, 32 // error 32-bit el0 #endifend(vectors)对kernel_ventry宏做解读:.align=7,说明该段代码是以2^7=128字节对齐的,这和向量表中每一个offset的大小是一致的
代码看似非常复杂,其实最终跳转到了b el()\\el()_\\label, 翻译一下,其实就是跳转到了如下这样的函数中
el1_sync_invalidel1_irq_invalidel1_fiq_invalidel1_error_invalidel1_sync:...el1_irq: kernel_entry 1 enable_da_f #ifdef config_trace_irqflags bl trace_hardirqs_off #endif irq_handler #ifdef config_preempt ldr x24, [tsk, #tsk_ti_preempt] // get preempt count cbnz x24, 1f // preempt count != 0 bl el1_preempt .macro irq_handler ldr_l x1, handle_arch_irq mov x0, sp irq_stack_entry blr x1 irq_stack_exit ... el1_fiq:...el1_error:...el0_sync:...el0_irq:...el0_fiq:...el0_error:...arm64中,当外部中断发生时, 会调用el1_irq, 在上述20行中,会call handle_arch_irq. 此为一个函数指针, 在gic中断控制器在做初始化时设置的. 而中断来临时,handle_domain_irq会被call 到.
//kernel/irq/handle.cint __init set_handle_irq(void(*handle_irq)(stuct pt_regs*)){ .... handle_arch_irq = handle_irq; ....}//drivers/irqchip/irq-gic.cstatic void _exception_irq_entry gic_handle_irq(struct pt_regs* regs){ ..... //调用对应的通用中断处理函数. handle_domain_irq(gic- >domain,irqnr,regs); .....}//drivers/irqchip/irq-gic.cstatic int __init __gic_init_bases(struct gic_chip_data *gic,int irq_start, struct fwnode_handle *handle){ .... set_handle_irq(gic_handle_irq);//中断向量指向的irq, 汇编语言会调用到此函数 ....}//drivers/irqchip/irq-gic.cstatic int gic_init_bases(struct gic_chip_data *gic, int irq_start,struct fwnode_handle *handle){ .... //分配irq_desc, 里面会call gic_irq_domain_map函数, 设置一吃handle的电信号处理函数 irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,numa_node_id()); ....}//driver/irqchip/irq-gic.cstatic int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,irq_hw_number_t hw){ struct gic_chip_data *gic = d- >host_data; //根据中断号的不同,处理不同的handle, driver 的实现依赖于硬件的gic 硬件spec, 参考规范 if (hw chip, d- >host_data,handle_percpu_devid_irq, null, null); irq_set_status_flags(irq, irq_noautoen); } else { //设置真正的pic的一级handle,处理pic的电信号 irq_domain_set_info(d, irq, hw, &gic- >chip, d- >host_data,handle_fasteoi_irq, null, null); irq_set_probe(irq); irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq))); } return 0;}//kernel/irq/chip.c//irq > 32的电信号处理函数, 下面几个是函数的层级,一步一步call到最终bsp注册处理的函数当中.void handle_fasteio_iq(struct irq_desc *desc){ ... handle_irq_event(desc); ...}//kernel/irq/handle.cirqreturn_t handle_irq_event(struct irq_desc *desc)){ ... handle_irq_event_percpu(desc); ...}//kernel/irq/handle.cirqreturn_t handle_irq_event_percpu(struct irq_desc *desc){ ... retval = __handle_irq_event_percpu(desc, &flags); add_interrupt_randomness(desc- >irq_data.irq, flags); ...}//kernel/irq/handle.c//电信号上半部最终处理的函数,这里面会去call bsp 工程师注册的上半部的函数irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags){ ... for_each_action_of_desc(desc, action) { ... res = action- >handler(irq, action- >dev_id);//设备driver的上半部callback函数 .... switch (res) { //上半部的返回值决定了下半部的触发方式,下节从bsp角度去看会用到. case irq_wake_thread: /**catch drivers which return wake_thread but did not set up a thread function*/ if (unlikely(!action- >thread_fn)) { warn_no_thread(irq, action); break; } __irq_wake_thread(desc, action);//唤醒线程化传输的下半部thread_fn /* fall through to add to randomness */ case irq_handled: *flags |= action- >flags; break; .... } } ...}//kernel/irq/handle.c//唤醒线程化传输的下半部thread_fn void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action){ ... wake_up_process(action- >thread); }通用中断框架:
//kernel/irq/irqdesc.hstatic inline int handle_domain_irq(struct irq_domain *domain,unsigned int hwirq, struct pt_regs *regs){ return __handle_domain_irq(domain, hwirq, true, regs);}//kernel/irq/irqdesc.cint __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_exit(); set_irq_regs(old_regs); return ret;}//kernel/irq/irqdesc.cint generic_handle_irq(unsigned int irq){ struct irq_desc *desc = irq_to_desc(irq); if (!desc) return -einval; generic_handle_irq_desc(desc); return 0;}//kernel/irq/irqdesc.hstatic inline void generic_handle_irq_desc(struct irq_desc *desc){ desc- >handle_irq(desc);//处理电信号类型相关函数.}通用中断会处理下面几件事情: 1. 上述第7行: 将一个pt_regs的指针变量保存
第10行更新系统一些统计量,开始进入上半部hardiro第20行核心处理函数,会调用注册的handle第22行 进入下半部执行.第23行,恢复pt_regs内核当中关于外部中断的关键数据结构以及关系:
以上handle_irq 与action之间的层次关系得出以下结论:
一个pic设备可以挂载多个handle_irq,也就是在/proc/interrupts 下面看到的具有相同的pic设备,反映到数据结构里面就是irq_chip 指向相同对象。一个irq_line(handle_irq) 可以挂载多个设备,这些设备共享irq号,以irq_action 里的dev_id来区分不同的具体设备,具体设备有具体的action。对于一个pic设备,其中断处理分为两级,第一级调用是irq_desc[irq].handle_irq, 第二级是特定的中断处理例程isr,在handle_irq的内部通过irq_desc[irq].action->handler调用。第一级函数在平台初始化时被安装到irq_desc数组中,第二级函数的注册发生在设备驱动程序调用request_irq安装对应设备的中断处理例程时。pic由内核/soc工程师完成,isr由bsp工程师完成。总结
中断控制器一般在soc当中(例如文中的gicv2)或者单独的一颗chip(例如早期x86的8259a),上文分析的为站到内核工程师的角度去分析pic部分, bsp工程师通常下driver时用的api能工作起来依靠上述架构, 下节我们继续分析站到bsp工程师角度从api方面看如何添加irq_action来让driver达到我们工作的预期.
荷航正在考虑未来只用波音777和787梦幻客机来进行长途运营飞行
电动汽车销量飙升 电池问题急需快速解决
V2X测试解决方案
碳化和硫化合成的的海绵状复合材料将提高电化学储能性能
天玑1100和unisoc t610区别
Linux内核外部中断解析(上)
智能家电亟待“适老化” 让老年人的晚年生活实现真正的智能化
浅谈LM1117 5V转4V电压的做法
如何使用阿里云ARMS诊断Java服务端报错问题
人工智能如何更好地实现人性化和建立自身可信度
OPPOR11发布会召开时间介绍,拍照旗舰全面升级!官微、代言人宣传给力
富士通亮相第29届智能交通世界大会
比三星S8惊艳!上个月发布的普拉达S8国产骄傲:售1699元
华为:拥抱数字化机遇,共建智能世界
明年年中内存的价格会重回涨势
“基因硬盘”千年存储:英国DNA新存储技术
中芯国际S2/FAB 8通过ISO 27001信息安全管理体
集成运放组成的多谐振荡器电路
为什么现在不应该购买比特币
智能音箱行业渐趋冷静 带屏或将成为新潮流