目录
通用数据结构
中断注册
中断线程化
中断处理
获取hwirq号
irq_enter
preempt_count
generic_handle_irq
irq_exit
通用数据结构
内核提供了两种中断描述符组织形式:
- 打开CONFIG_SPARSE_IRQ宏(中断编号不连续),中断描述符以radix-tree来组织,用户在初始化时进行动态分配,然后再插入radix-tree中;
- 关闭CONFIG_SPARSE_IRQ宏(中断编号连续),中断描述符以数组的形式组织,并且已经分配好;
- 不管哪种形式,最终都可以通过linux irq号来找到对应的中断描述符;
- 图的左侧灰色部分,主要在中断控制器驱动中进行初始化设置,包括各个结构中函数指针的指向等,其中struct irq_chip用于对中断控制器的硬件操作,struct irq_domain与中断控制器对应,完成的工作是硬件中断号到Linux irq的映射;
- 在设备申请注册中断的过程中进行设置,比如struct irqaction中handler的设置,这个用于指向我们设备驱动程序中的中断处理函数;irqaction以链表的形式组织,Irq_desc中存的是irqaction链表的头结点
中断注册
- irq:要申请中断的中断号。
- handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
- flags:中断标志。比如按键按下后为低电平,那么可以设置为下降沿触发
- name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名
- dev: 对于共享中断,通常通过dev_id来查询寄存器,确定哪个外设产生的中断,一般情况下将dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
- request_irq也是调用request_threaded_irq,只是在传参的时候,线程处理函数thread_fn函数设置成NULL
Tips:
- 共享中断:多个设备共享一个中断号,需要硬件支持以区分哪个设备发生的中断
- 中断共享,每个中断号有一个中断描述符,每个设备有自己的中断处理函数。所以irqaction会形成链表。old指向irqaction链表头节点,old不为空说明已经有中断添加到中断描述符irq_desc中,说明这是一个共享中断。shared=1。
- 对于ONESHOT的解释:对于共享中断,如果某个中断处理程序设置了ONESHOT标志,则此中断的thread mask (每个irqaction有一个thread_mask位图)将被设置。每个中断处理程序完成后unmask一位,表示解除对中断的屏蔽,从而允许其他中断处理程序处理该中断。当所有中断处理程序完成后,中断线程掩码才被清零,从而允许下一次的中断被触发。
- 解释:为什么是电平触发?如果是高电平触发,则电平由低到高后,中断触发,然后电平会维持高电位直到中断处理过程中(irq_activate用于激活一个中断,从而允许中断响应和处理),软件写入外设中的状态寄存器时,电平才会下降。而边沿触发,中断触发后,电平就会恢复
实验:去掉IRQF_ONESHOT的判定,结果手机开机卡在vivo界面,不断重启。
分析:因为没有了这处判断,导致使用默认中断处理函数并且没设置ONESHOT标志的中断注册成功。当发生中断时,中断就会连续不断触发,
中断线程化
好处:
- 降低内核中的延迟:中断线程化后进一步压缩了上半部的工作量,上半部的工作仅仅需要完成 “快速检查”,譬如确保中断的确来自期望的设备;如果检查通过,它将对硬件中断完成确认并通知内核唤醒中断处理线程完成中断处理的下半部。
- 由于中断线程化后简化乃至避免了整个中断处理流程中 “关中断处理(术语上称之为 “hard” 部分)” 和 “开中断处理(术语上称之为 “soft” 部分)” 两个阶段之间可能涉及的锁同步机制,从而降低了整体实现上的复杂性。中断处理线程化还有助于内核的调试。
- 内核线程运行时,发生中断,当前线程的内核栈仍然会被用作中断栈
__setup_irq函数的一部分:
对于非嵌套中断,如果可以线程化,则默认对其强制线程化(将上半部处理函数线程化),之后如果存在thread_fn,则也会将其线程化
通过这种方式,使用当前的irqaction和它的secondary将handler和thread_fn均线程化处理,而真正放在中断上半部处理的只是irq_default_primary_handler
通过这种方式,大大减少了上半部处理的工作量。
中断处理
获取hwirq号
当进入异常处理程序时,软件不知道它所接受的是哪个中断。处理程序必须读取一个Interrupt Acknowledge Registers(IARs)来获取中断的INTID。
有两个IAR:
中断号1023表示无效中断。
irq_enter
首先,irq_enter主要执行为
整个过程主要操作可抽象成
这个过程就是将抢占计数(默认为0)加上HARDIRQ_OFFSET,HARDIRQ_OFFSET可知操作的是硬件中断计数器。所以irq_enter就是通过preempt_count告诉系统,现在正在处理中断的上半部分工作(在irq_enter和irq_exit函数之间),不可以进行抢占。
preempt_count
preempt_count这个成员被用来判断当前进程是否可以被抢占。如果preempt_count不等于0,说明当前不能进行抢占,如果preempt_count等于0,说明已经具备了抢占的条件(当然具体是否要抢占当前进程还是要看看thread info中的flag成员是否设定了_TIF_NEED_RESCHED这个标记,可能是当前的进程的时间片用完了,也可能是由于中断唤醒了优先级更高的进程)。
- preemption count:用来记录当前被显式的禁止抢占的次数,也就是说,每调用一次preempt_disable,preemption count就会加一,调用preempt_enable,该区域的数值会减去一。preempt_disable和preempt_enable必须成对出现,可以嵌套,最大嵌套的深度是255。
- hardirq count:描述当前中断handler嵌套的深度。hardirq count占用了4个bit,说明硬件中断handler最大可以嵌套15层(实际上中断的嵌套已经不会发生了。因此,理论上,hardirq count要么是0,要么是1。不过呢,万一有人在handler中打开中断,那么这时候中断嵌套还是会发生的。但是,应该不会太多,因此,目前hardirq count占用了4个bit,应付15个奇葩driver是妥妥的)preempt_count_add(HARDIRQ_OFFSET)。preempt_count_sub(HARDIRQ_OFFSET)用于操作此段
- softirq count:与hardirq count类似。kernel提供了local_bh_enable和local_bh_disable这样的接口函数来加减此段
Tips:为什么中断不能嵌套?
- 防止栈空间不够用。
因为每个中断被执行,都要先保存现场。一个中断栈空间有限
- 中断上半部的处理应当越快越好。否则无法及时响应一些紧急任务
generic_handle_irq
- 硬件中断号小于32,说明是SGI或者PPI,handle_irq设置为handle_percpu_devid_irq
- 硬件中断号大于等于32,说明是SPI,handle_irq设置为handle_fasteoi_irq,在此函数中会判断是否设置了ONESHOT标志,如果设置,则会屏蔽此中断请求。
中断处理程序有三种可能的返回值。
- 唤醒内核线程后,执行irq_thread 函数在一个循环中调用thread_fn
- 有两种情况会返回IRQ_WAKE_THREAD,一种是没有提供上半部处理函数handler,使用默认的处理函数,直接返回IRQ_WAKE_THREAD。一种是自定义了handler,则如果线程化,返回值必须是IRQ_WAKE_THREAD
- 在thread_fn中,如果中断被正确处理,返回值是IRQ_HANDLED
irq_exit
irq_exit()
函数在每次中断处理程序(上半部)完成后调用,以便退出中断上下文,重新启用中断(cpu此时相当于空闲了,可以去处理其他中断了)并处理softirq。
核心功能代码:
- preempt_count_sub:preempt_count的硬中断位减一,表示退出硬中断上下文。
- in_interrupt:判断当前是否处于硬中断上下文,如果处于返回一个非零值。
- local_softirq_pending :判断__softirq_pending的值是否为0,如果不为0表示软中断pending,__softirq_pending的不同bit位表示不同的软中断,在触发软中断时会设置。
- invoke_softirq:对软中断进行处理