一、介绍
1、中断本质是一种特殊的电信号,由硬件设备产生,交由中断控制器后传递给cpu处理。
其基本传递流程如下:
- 硬件设备产生中断电信号,并将信号输入中断控制器引脚
- 中断控制器发信号给处理器,处理器中断当前工作
- 处理器通知操作系统产生了中断,操作系统进行处理。
下面介绍中断处理机制时有中断从硬件到内核的路由。
- 该问提及的中断都是属于中断的上半部,也即硬中断,执行在硬中断上下文中。
- 中断的下半部是中断处理的推后处理的工作,内核代码里叫软中断部分,但是中断下半部不止软中断,还有tasklet和工作队列,这些后文会讲。
- 下半部:内核中凡是属于推后处理的工作都叫下半部。
2、中断线与中断号
中断不需要与系统时钟保持同步_,随时都能产生中断。
不同设备对应不同中断,每个中断通过唯一的数字标志。
中断值也称中断请求线(IRQ),每个IRQ都会被关联一个数字。
x86-32位系统中断号共256个。
前0x20=32个用于内中断,供内核自己使用,初始化在arch/x86/kernel/traps.c。
0x80是用于系统调用然后陷入内核。
0×20~0x7F、0×81~0xFF用于外中断,初始化在arch/x86/kernel/irqinit.c。
3、异常与中断
异常:是cpu主动引起,并且必须要与处理器时钟同步,也叫同步中断。 中断:是cpu除外的硬件产生
因为内核处理异常跟处理中断的机制差不多,所以讨论中断时就包含了异常的情况。
4、上半部
- 上半部等价于中断处理程序的那部分,是要快速执行完的,而上半部也只能由中断处理程序来实现。
- 下半部是需要推后执行的那部分中断处理部分。有很多种实现方式,2.5以后再用的有:软中断、tasklet和工作队列。
- 内核中所有可以推后执行的程序都可以叫下半部。
内中断 = 异常
1、异常(exception)
cpu在执行进程时产生。
包括陷入(trap)、出错(fault)和 终止(abort)等
陷入:比如除0错误,就会陷入异常,陷入返回执行的是下条指令;调试时打断点也是这种模式,断点执行后,执行的是下条指令。
出错:比如缺页异常,就会出错进入异常,出错处理后执行的是当前指令,也就是当前指令会被再次执行。
2、可编程中断(programmable exception)
可编程中断:由int指令触发,比如linux中唯一的一个中断号int 0x80,作为系统调用。
二、外中断 = 中断
初始化代码:arch/x86/kernel/irqinit.c
硬中断:由硬件产生的信号传给cpu产生的上半部处理。
软中断:由硬件产生中断后交由下半部中断处理程序处理。
三、中断控制
中断控制方法列表:
| 序号 | 函数 | 说明 | 统计 | 范围 |
|---|---|---|---|---|
| 1 | local_irq_disable() | 禁止本地中断传递 | x86中,仅仅是执行了cli和sti指令,对应的操作是clear和set,即只修改允许中断标志(allow interrupt)。 | 当前cpu |
| 2 | local_irq_enable() | 激活本地中断传递 | ||
| 3 | local_irq_save() | 保存本地中断传递的当前状态,然后禁止本地中断传递。 | 保存或恢复状态,并修改标志 | |
| 4 | local_irq_restore() | 恢复本地中断传递到给定的状态 | ||
| 5 | disable_irq(irq) | 禁止给定的本地中断线,并等到该中断线上执行的处理程序结束后才返回,即返回时该线上不会有运行的处理程序。 | - 这四个函数可以嵌套。 - 5和6的分别调用几次,8就要调用几次才能最终激活该中断线。 - 5、6、7可以在中断或进程上下文中执行,而且不会睡眠,即不会产生上下文切换。(在中断上下文中调用时,要注意当中断线上有处理程序在运行时,并不能激活它)。 |
给定中断线 |
| 6 | disable_irq_nosync(irq) | 禁止给定本地中断线,不管该中断线上是否有在执行的处理程序。 | ||
| 7 | synchronize_irq(irq) | 等待一个特定的中断处理程序的退出,一直等。 | ||
| 8 | enable_irq(irq) | 激活给定中断线 | ||
| 9 | irqs_disabled() | 本地中断传递被禁止,就返回非0,否则是0。 | 判断中断系统的状态 | 当前cpu |
| 10 | in_interrupt() | 在中断上下文中就返回非0,进程上下文中返回0。 | ||
| 11 | in_irq() | 当前正在运行中断处理程序就返回非0,否则返回0。 | ||
| 12 | in_softirq() | 在下半部上下文中就返回非0,否则返回0。 |
四、中断处理机制
一)、中断路由
二)、以x86为例
1、内核汇编代码
接收中断的代码定义在arch/x86/kernel/entry_32.S和entry_64.S中。
2、do_IRQ()
定义在x86/kernel/irq.c中:
/** do_IRQ会处理所有普通设备的中断响应,特殊的smp跨cpu中断有自己的指定的处理方式。* do_IRQ handles all normal device IRQ's (the special* SMP cross-CPU interrupts have their own specific* handlers).*/unsigned int __irq_entry do_IRQ(struct pt_regs *regs){struct pt_regs *old_regs = set_irq_regs(regs);/* high bit used in ret_from_ code */unsigned vector = ~regs->orig_ax;unsigned irq;exit_idle();irq_enter(); // 主要是增加当前进程里硬中断的计数:thread_info->preempt_count// irq_enter()->__irq_enter()->add_preempt_count()【include/linux/hardirq.h】-> preempt_count()+=val【include/linux/preempt.h】 -> current_thread_info()->preempt_count + valirq = __get_cpu_var(vector_irq)[vector]; // 采用不加锁的方式获取当前cpu上的变量,这里是获取vector处的值,即上面所计算得到的中断号// 如果配合preempt_disable(); 可以禁止本地中断来获取加锁变量。// 执行中断,声明在x86/include/asm/irq.h,定义在x86/kernel/irq_32.c中。// 最终会调用到handle_IRQ_event(),定义在kernel/irq/handle.c中if (!handle_irq(irq, regs)) {ack_APIC_irq();if (printk_ratelimit())pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n",__func__, smp_processor_id(), vector, irq);}irq_exit(); // 减少硬中断的计数// 恢复中断前cpu状态set_irq_regs(old_regs);return 1;}
3、irq_enter()
进入硬中断上半部
/** 标识进入一个中断上下文* Enter an interrupt context.*/void irq_enter(void){int cpu = smp_processor_id(); //rcu_irq_enter();if (idle_cpu(cpu) && !in_interrupt()) {__irq_enter();tick_check_idle(cpu);} else__irq_enter();}
5、handle_IRQ_event()
运行中断线上所安装的所有中断处理程序
定义在kernel/irq/handle.c中:
/*** 由每个体系架构的do_IRQ调用此函数来运行要处理的中断线上安装的中断处理程序。* - do_IRQ会判断该中断线上是否有处理程序并且已启动但没有执行。* - 在include/linux/irq.h中声明。* handle_IRQ_event - irq action chain handler* @irq: the interrupt number 指定的中断号(中断线)* @action: the interrupt action chain for this irq 该中断线上注册的处理程序,可能是多个或根本没注册就不执行** Handles the action chain of an irq event*/irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action){irqreturn_t ret, retval = IRQ_NONE;unsigned int status = 0;// IRQF_DISABLED表示需要禁止中断的情况下才执行中断处理程序// 所以当前处理程序如果没指定禁止中断,那就先启用本地中断,以让cpu正常接收其他中断。if (!(action->flags & IRQF_DISABLED))local_irq_enable_in_hardirq();// 循环执行所有中断处理程序,除非是非共享的那就会直接结束循环。do {trace_irq_handler_entry(irq, action);ret = action->handler(irq, action->dev_id);trace_irq_handler_exit(irq, action, ret);switch (ret) {case IRQ_WAKE_THREAD:/** 返回值设为已处理,以便可疑的检查不再触发* Set result to handled so the spurious check* does not trigger.*/ret = IRQ_HANDLED;/** 捕获返回值为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;}/* 为这次中断唤醒处理线程* - 万一线程崩溃且被杀死,只能假装中断处理完了* - 如果需要,硬中断在上面并没有打开还是禁止状态,防止中断再产生* Wake up the handler thread for this* action. In case the thread crashed and was* killed we just pretend that we handled the* interrupt. The hardirq handler above has* disabled the device interrupt, so no irq* storm is lurking.*/if (likely(!test_bit(IRQTF_DIED,&action->thread_flags))) {set_bit(IRQTF_RUNTHREAD, &action->thread_flags); // 设置处理程序的线程正在运行的标识wake_up_process(action->thread); // 唤醒执行线程}/* Fall through to add to randomness */case IRQ_HANDLED:status |= action->flags;break;default:break;}// 执行下个处理程序retval |= ret;action = action->next;} while (action);// 如果设置了IRQF_SAMPLE_RANDOM,中断处理完后为随机数产生器产生熵。if (status & IRQF_SAMPLE_RANDOM)add_interrupt_randomness(irq);// 默认要禁止本地所有中断的,只修改禁止的标识local_irq_disable();return retval;}
