值得一看的CPU与linux内核对中断的处理

【推荐阅读文章】

浅谈linux 内核网络 sk_buff 之克隆与复制

深入linux内核架构--进程&线程

了解Docker 依赖的linux内核技术

浅析linux内核网络协议栈--linux bridge

深入理解SR-IOV和IO虚拟化

CPU的工作

如图 6.11 所示为中断或异常发生时,CPU 所要做的工作。

确定中断或异常的中断向量 i(在 0~255 之间)。可屏蔽中断的中断向量从中断控制器获得,不可屏蔽中断和异常的中断向量是固定的。

通过 IDTR 寄存器找到 IDT,读取第 i 项(即第 i 个门描述符)。

进行特权级检查。将 CPU 的当前特权级 CPL 与门描述符的 DPL 相比较,如果 CPL 小于 DPL,则能够通过门,否则产生通用保护异常(中断向量 13)

。通过门之后,获取门描述符中段选择符所指

向代码段的 DPL,将其与 CPL 相比较,如果小于,则执行中断处理程序,否则产生一个通用保护异常。

若上述过程检测到特权级发生了变化,则需要进行堆栈的切换。因为中断或异常服务程序运行在内核态(特权级为 0),当中断或异常在 CPU 处于用户态(特权级为 3)时发生,将引起堆栈的切换。

也就是说,从用户态堆栈切换到内核态堆栈。如果当前是异常,则 CPU 将异常码压入当前的堆栈。

如果当前是中断,CPU 清零 EFLAGS 的 IF 位,即关闭所有的可屏蔽中断;如果是异常,CPU 不会清零该位。进入中断或异服务程序并执行。

值得一看的CPU与linux内核对中断的处理_第1张图片

内核对中断的处理

图 6.12 所示为中断在内核中的处理过程。

值得一看的CPU与linux内核对中断的处理_第2张图片

所有中断(除去不可屏蔽中断)的服务程序在 init_IRQ 函数中都被初始化为 interrupt[i],interrupt 数组的每一项指向一个代码片段,该代码片段除了将中断向量号压入堆栈外,还要调用到一个公共的处理程序common_interrupt。

613 common_interrupt:
614    SAVE_ALL
615    TRACE_IRQS_OFF
616    movl %esp,%eax
617    call do_IRQ
618    jmp ret_from_intr

common_interrupt 函数首先保存现场,将中断发生前所有寄存器的值(属于被中断的进程)保存在堆栈中,然后调用 do_IRQ 函数。

do_IRQ 函数在 arch/i386/kernel/irq.c 文件中定义为:

071 fastcall unsigned int do_IRQ(struct pt_regs *regs)
072 {
073 ......
/* 取得中断向量号 */
075 int irq = ~regs->orig_eax;
076 ......
/* 进入中断上下文 */
089 irq_enter();
090 ......
/* 调用该 IRQ 的公共处理程序对中断进行处理 */
143 desc->handle_irq(irq, desc);
144 /* 退出中断上下文 */
145 irq_exit();
147 ......
148 }

从堆栈中取出中断向量号之后,do_IRQ 函数调用 irq_enter 进入中断上下文。irq_enter 函数主要的工作是增加被中断进程的 preempt_count 计数器的值,表示禁止抢占。在中断处理结束调用 irq_exit 退出中断上下文时,会相应地减少该计数器的值。

do_IRQ 函数的工作重心在第 143 行,调用每个 IRQ 线的公共服务程序。

在通用 IRQ 层引入之前,每个 IRQ 线并没有自己单独的服务程序,而是所有的 IRQ 线都共用一个公共的服务程序__do_IRQ 对中断进行处理。

在通用 IRQ 层引入之后,对不同类型中断(边沿触发、电平触发等)的处理流程做出了区分,并分别设 置 了 一 些 对 应 的 服 务 程 序 , 比 如 边 沿 触 发 中 断 对 应 handle_edge_irq 函 数 , 电 平 触 发 中 断 对 应handle_level_irq 函数等。

无论是旧的__do_IRQ 函数还是新的各种类型中断单独的服务程序,它们的基本原理还是一样的。都要首先屏蔽当前的 IRQ,禁止该中断线上的中断传递。

然后,会判断该 IRQ 线的中断请求队列上是否为空,即是否有一个或多个有效的中断服务程序,如果有的话,就调用 handle_IRQ_event 函数来遍历并执行挂载在该 IRQ 线中断请求队列上的所有中断处理程序。

handle_IRQ_event 函数在 arch/kernel/irq/handle.c 文件中定义为:(该函数可以被所有体系共用,而do_IRQ是不同体系定义不同的,可见这个handle_IRQ_event的通用性)

129 irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
130 {
131 irqreturn_t ret, retval = IRQ_NONE;
132 unsigned int status = 0;
133
134handle_dynamic_tick(action);
135
/* 因为 CPU 在通过中断门时自动将中断关闭,如果这里希望将它们打开,即希望
* 中断服务程序能够在中断打开的情况下执行,就需要在使用 request_irq 函数
* 注册中断处理程序时,不要设置 IRQF_DISABLED 标志。
*/
136if (!(action->flags & IRQF_DISABLED))
137local_irq_enable_in_hardirq();
/* 遍历该 IRQ 线的中断请求队列,并调用其中的所有中断服务程序。通常每个具体的
* 中断服务程序中都会一开始检查各自的中断源,判断是否有来自该设备的中断请求,
* 如果没有则马上退出。而且每个 IRQ 线的中断请求队列一般也不会很大,所以这个
* 过程并不会耗费很长的时间。
*/
139do {
140 ret = action->handler(irq, action->dev_id);
141 if (ret == IRQ_HANDLED)
142status |= action->flags;
143 retval |= ret;
144 action = action->next;
145} while (action);
146
/* 如果注册 IRQ 时设置了 IRQF_SAMPLE_RANDOM 标志,则需要调用
* add_interrupt_randomness 函数,以中断间隔时间为随机数产生熵。
*/
147if (status & IRQF_SAMPLE_RANDOM)
148add_interrupt_randomness(irq);
/* 最后关闭中断,函数返回。*/
149local_irq_disable();
150
151return retval;
152 }

定义了do_IRQ的文档,不同体系都有自己的定义
 #   line  filename / context / line
   1     58  arch/avr32/mach-at32ap/intc.c <>
             asmlinkage void do_IRQ(int level, struct pt_regs *regs)
   2     86  arch/cris/kernel/irq.c <>
             asmlinkage void do_IRQ(int irq, struct pt_regs * regs)
   3    141  arch/frv/kernel/irq.c <>
             asmlinkage void do_IRQ(void )
   4    171  arch/h8300/kernel/irq.c <>
             asmlinkage void do_IRQ(int irq)
   5     72  arch/m32r/kernel/irq.c <>
             asmlinkage unsigned int do_IRQ(int irq, struct pt_regs *regs)
   6     21  arch/m68knommu/kernel/irq.c <>
             asmlinkage void do_IRQ(int irq, struct pt_regs *regs)
   7     36  arch/microblaze/kernel/irq.c <>
             void __irq_entry do_IRQ(struct pt_regs *regs)
   8    159  arch/mips/kernel/irq.c <>
             void __irq_entry do_IRQ(unsigned int irq)
   9    156  arch/mn10300/kernel/irq.c <>
             asmlinkage void do_IRQ(void )
  10    384  arch/powerpc/kernel/irq.c <>
             void do_IRQ(struct pt_regs *regs)
  11     48  arch/score/kernel/irq.c <>
-- More --

中断处理结束之后,do_IRQ 函数返回到 common_interrupt(公共的处理程序),然后调用汇编函数 ret_from_intr 从中断返回。

如果内核正在从中断返回到用户空间,并且被中断进程设置了标志 TIF_NEED_RESCHED,则ret_from_intr 会调用 schedule 函数进行重新调度。

current->thread_info.flags中TIF_NEED_RESCHED为1,表示当前进程需要执行schedule()释放CPU控制权.

如果内核正在返回内核空间,即中断了内核本身,则ret_from_intr 会检查被中断进程的 preempt_count 计数器是否为 0,只有为 0 时,schedule 函数才会被调用(不为0表示此中断服务加锁,抢占这个中断不安全,不允许抢占;而schedule函数作用是即刻中断当前服务,对于加锁进程来说,是不安全的,所以只有为0的时候允许调用schedule)。

你可能感兴趣的:(linux,运维,服务器)