目录
4 IPI(Interrupt-Procecesorr Interrupt):处理器中间的中断
1 基础知识
1.1 同步中断和异步中断
同步中断是指当指令执行时由 CPU 控制单元产生的,之所以称为同步,是因为只有在一条指令终止执行后 CPU 才会发出中断。异步中断是由其他硬件设备依照 CPU 时钟信号随机产生的。 《深入理解 LINUX 内核》P135
异常与中断不同,它在产生时必须考虑与处理器时钟同步。实际上,异常也常常称为同步中断。在处理器执行到由于编程失误而导致的错误指令的时候,或者在执行棋局出现特殊情况(如缺页),必须由内核来处理的时候,处理器就会产生一个异常。 《Linux 内核设计与实现》P92
1.2 中断上下文
当执行一个中断处理程序时,内核处于中断上下文 (interrupt context) 中。
因为没有后备进程,所以中断上下文不可以睡眠,否则有怎能再对它重新调度呢?因此,不能在中断上下文中调用某些函数。如果一个函数睡眠,就不能在你的中断处理程序中使用它——这是对什么样的函数可以在中断处理程序中使用的限制。
中断上下文具有较为严格的时间限制,因为它打断了其它代码。中断上下文的代码应当迅速、简洁,尽量不要使用循环去处理繁重的工作。
《Linux 内核设计与实现》P99
1.3 中断处理程序栈
中断处理程序栈的设置是一个配置选项。曾经,中断处理程序并不具有自己的栈。相反,它们共享所中断进程的内核栈。内核栈的大小是两页,具体地说,在 32 位体系结构上是 8KB,在 64 位体系结构上是 16KB。因为在这种设置中,中断处理程序共享别人的堆栈,所以它们在栈中获取空间时必须非常节约。
在 2.6 版早期的内核中,增加了一个选项,把栈的大小从两页减到一页。这就减轻了内存的压力,因为系统中每个进程原先都需要两页连续,且不可换出的内核内存。为了应对栈大小的减少,中断处理程序拥有了自己的栈,每个处理器一个,大小为一页。
《Linux 内核设计与实现》P99 《Linux 内核设计与实现》P203
1.4 中断处理程序的重入
linux 中的中断处理程序是无须重入的。当一个给定的中断处理程序正在执行时,相应的中断线在所有处理器上都会被屏蔽,以防止在同一中断线上接收另一个新的中断。通常情况下,所有其他的中断都是打开的,所以这些不同中断线上的其它中断都能被处理,但当前中断线是被禁止的。由此可以看出,同一个中断处理程序绝对不会被同时调用以处理嵌套的中断。这极大地简化了中断处理程序的编写。
《Linux 内核设计与实现》P97
1.5 中断的返回
从中断处理程序或系统调用返回的返回路径都是体系结构相关的,在 entry.S 文件中通过汇编语言来实现。《Linux 内核设计与实现》P53
1.6 中断控制器
2 顶半部和底半部(推后执行的工作)
2.1 简介
中断处理的一个主要问题是怎样在处理例程内完成耗时的任务。响应一次设备中断需要完成一定量的工作,但是中断处理例程需要尽快结束而不能使中断阻塞的时间过长,这两个需求(工作和速度)彼此冲突,让驱动程序的作者多少有点困扰。
Linux (连同很多其他的系统)通过将中断处理例程分成两部分来解决这个问题。称为”顶半部“的部分,是世纪响应中断的例程,也就是 request_irq() 注册的中断例程;而”底半部“是一个被顶半部调度,并在稍后更安全的时间内执行的例程。
顶半部处理例程和底半部处理处理例程之间最大的不同,就是底半部处理例程执行时,所有的中断都是打开的——这就是所谓的在更安全的时间内运行。
《LINUX 设备驱动程序》(第三版)P274
底半部机制的演化历程
下半部机制 | 状态 |
BH | kernel 2.5中移除 |
任务队列(Task queues) | kernel 2.5中移除 |
软中断 | kernel 2.3开始引入 |
tasklet | kernel 2.3开始引入 |
工作队列(Work queues) | kernel 2.5开始引入 |
2.2 软中断
软中断的分配是静态的(即在编译时定义)。
软中断(即便是同一种类型的软中断)可以并发地运行在多个 CPU 上,因此,软中断是可重入函数而且必须明确地使用自旋锁保护其数据结构。
《深入理解 LINUX 内核》P174
软中断保留给系统中对时间要求最严格以及最重要的底半部使用。目前,只用两个子系统(网络和 SCSI)直接使用软中断。
《Linux 内核设计与实现》P113
软中断的代码位于 kernel/softirq.c 文件中。
kernel/softirq.c 中定义了一个包含有 32 个 softirq_action 结构体的数组:
static struct softirq_action softirq_vec[NR_SOFTIRQS];
因此最多可能有 32 个软中断。
在当前版本的内核中,这 32 个中只用到 9 个。 // 《深入理解 LINUX 内核》P175 上介绍只用了 6 个。
一个软中断不会抢占另一个软中断。实际上,唯一可以抢占软中断的是中断处理程序。不过,其它的软中断(甚至是相同类型的软中断)可以在其它处理器上同时执行。
《Linux 内核设计与实现》P111
2.3 tasklet
tasklet 通常是底半部处理的优先机制;因为这种机制非常快,但是所有的 tasklet 代码必须是原子的。
tasklet 是一个由系统决定的安全时刻在软件中断上下文被调度运行的特殊函数。 //tasklet 是利用软中断实现的
《LINUX 设备驱动程序》(第三版)P275
两个不同类型的 tasklet 可以在不同的处理器上同时运行,但类型相同的 tasklet 不能同时执行。tasklet 其实是一种在性能和易用性之间寻求平衡的产物。 《Linux 内核设计与实现》P109
内核对 tasklet 的执行进行了更加严格的控制。相同类型的 tasklet 总是被串行地执行,换句话说:不能在两个 CPU上同时运行相同类型的 tasklet。tasklet 的串行化使 tasklet 函数不必是可重入的,因此简化了设备驱动开发者的工作。 《深入理解 LINUX 内核》P174
tasklet 是利用软中断实现的一种底半部机制。它和进程没有任何关系。《Linux 内核设计与实现》P114
2.4 工作队列
工作队列工作在进程上下文,因此可在必要的时休眠。《LINUX 设备驱动程序》(第三版)P277
工作队列可以把工作推后,交由一个内核线程去执行——这个底半部总是会在进程上下文中执行。这样,通过工作队列执行的代码占尽进程上下文的所有优势。最重要的就是工作队列允许重新调度甚至是睡眠。 《Linux 内核设计与实现》P120
2.5 内核定时器
不像 软中断、tasklet 和工作队列,内核定时器把操作推迟到某个确定的时间段之后执行。 《Linux 内核设计与实现》P114
2.6 底半部机制的选择
对于大部分底半部的处理来说,用 tasklet 就足够了,像网络这样对性能要求非常高的情况才需要用软中断。可是,使用软中断需要特别小心,因为两个相同的软中断有可能同时执行。此外,然中断还必须在编译期间就进行静态注册。与此相反,tasklet 可以通过代码进行动态注册。
《Linux 内核设计与实现》P109
软中断保留给系统中对时间要求最严格以及最重要的底半部使用。目前,只用两个子系统(网络和 SCSI)直接使用软中断。
《Linux 内核设计与实现》P113
如果退后执行的任务需要睡眠,那么就选择工作队列。如果退后执行的任务不需要睡眠,那么就选择软中断或者tasklet。
如果你需要一个可以重新调度的实体来执行你的底半部处理,你应该使用工作队列。它是唯一能在进程上下文运行的底半部实现机制,也只有它才可以睡眠。这意味着在你需要获取大量内存时,在你需要获取信号量时,在你需要执行阻塞式的 I/O 操作时,工作队列会非常有用。
《Linux 内核设计与实现》P120
3 中断亲和性
在多处理器系统中,管理员可以设置中断亲和性,允许中断控制器把某个中断转发给哪些处理器,有两种配置方法。
<1> 写文件”/proc/irq/irq_id/smp_affinity”,参数是位掩码。
<2> 写文件“/proc/irq/irq_id/smp_affinity_list”,参数是处理器列表
例如,管理员允许中断控制器把 Linux 中断号为 32 的中断转发给处理器 0~3,配置方法有两种
<1> echo 0f > /proc/irq/32/smp_affinity
<2> echo 0-3 > /proc/irq/32/smp_affinity_list
《Linux 内核深度解析》P431
4 IPI(Interrupt-Procecesorr Interrupt):处理器中间的中断
处理器间中断是一种特殊的中断,在多处理器系统中,一个处理器可以向其它处理器发送中断,要求目标处理器执行某件事情。
常见的使用处理器间中断的函数
<1> int smp_call_function(smp_call_func_t func, void *info, int wait);在所有其它处理器上执行一个函数。
<2> int smp_call_function_single(int cpu, smp_call_func_t func, void *info, int wait);在指定的处理器上执行一个函数。
<3> void smp_send_reschedule(int cpu);要求指定的处理器重新调度进程。
《Linux 内核深度解析》P432
5 中断线程化
6 软中断
7 中断计数
a 调试
a.1 /proc/ 目录下中断相关的文件
a.1.1 /proc/interrupts
root@ubuntu:~# cat /proc/interrupts
CPU0 CPU1
0: 59 0 IO-APIC 2-edge timer
1: 7543 5860 IO-APIC 1-edge i8042
4: 10500 20397 IO-APIC 4-edge
6: 2 0 IO-APIC 6-edge floppy
7: 0 0 IO-APIC 7-edge parport0
8: 1 0 IO-APIC 8-edge rtc0
9: 0 0 IO-APIC 9-fasteoi acpi
12: 83474 85761 IO-APIC 12-edge i8042
14: 59071 30683 IO-APIC 14-edge ata_piix
15: 13283 11485 IO-APIC 15-edge ata_piix
16: 296481 162261 IO-APIC 16-fasteoi vmwgfx, snd_ens1371
17: 44 0 IO-APIC 17-fasteoi ehci_hcd:usb1, ioc0
18: 70 0 IO-APIC 18-fasteoi uhci_hcd:usb2
19: 31649 15122 IO-APIC 19-fasteoi eth0
......
该文件只会显示那些已经安装了中断处理程序的中断。 //如果想看所有中断的中断计数,请看/proc/stat 文件
a.1.2 /proc/stat
root@ubuntu:~# cat /proc/stat
cpu 424459 1644 337387 4043890 33456 0 5679 0 0 0
cpu0 215069 901 169850 2016112 17343 0 2680 0 0 0
cpu1 209390 742 167536 2027778 16113 0 2999 0 0 0
intr 23011873 59 13490 0 0 30957 0 2 0 1 0 0 0 171563 0 89830 24818 463827 44 70 46841 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 75204879
btime 1600220115
processes 10868
procs_running 4
procs_blocked 0
softirq 6547424 1 2740923 5072 105003 102292 0 13168 2059115 0 1521850
root@ubuntu:~#
/proc/stat 记录了一些系统活动的底层统计信息,包括(但不仅限于)从系统启动开始接收到的中断数量,stat 每一行都以一个字符串开始,它是这行的关键字。intr 标记正是我们需要的 《LINUX 设备驱动程序》(第三版)P263
......
intr 2276403 59 2275 0 0 11404 0 2 0 1 0 0 0 37025 0 48738 9364 1649 45 72 55296 0 0 0 0 0 0 0 0 0 0......
......
第一个数是所有中断的总数,而其它的每个数都代表一个单独的 IRQ 信号线,从中断 0 开始。
上面的数据显示:
0 号中断发生了 59 次
1 号中断发生了 2275 次
2 号中断发生了 0 次
3 号中断发生了 0
4 号中断发生了 11404 次
a.1.3 /proc/softirqs
a.2 统计处理 硬中断 的时间
BCC工具:hardirqs-bpfcc
命令:mpstat
a.3 统计处理 软中断 的时间
BCC工具:softirqs-bpfcc
命令:mpstat