首页 Linux 中断
文章
取消

Linux 中断

专业名词含义
irq/中断信号Interrupt Request
hwirqhardware irq 硬件中断号
中断向量号cpu规定的中断标准号
IPIinter processer interrupt,处理器间中断
APICAdvance Processer Interrupt Component
中断线每个外设与每个中断控制相连的线,相当与一个通道。
可以多个设备共用
ISRInterrupt Service Routine

一. 中断简介

CPU是整个现代操作系统的全能蓝领工人,由它负责协调处理整个系统在运行过程中需要的数据和数据的处理。操作系统就像一个大工厂,在这个大工厂上每个应用程序都是一条流水线, 这些流水线在运行过程中会主动请求获取数据进行加工,也有可能是流水线传东西过来给它被动地进行数据加工。不管是主动的获取数据还是被动地获取数据,都需要工厂的协调, 也就是需要操作系统的协调。这个协调就是中断了。各种外设就是操作系统这个大工厂的各种数据的供应商,他们与操作系统之间的沟通主要就是通过中断消息,有对外的数据供应商,如网卡,键盘,鼠标;有对内数据供应商,如磁盘。

1.1 大白话说中断

1.1.1 磁盘IO中断

io中断的场景类比,当流水线操作到某一个步骤,需要一些不在流水线上面的数据的时候,就会告知工厂帮我拿这些这些数据给我,我要用。这个时候操作系统工厂就会分一个篮子,然后通知磁盘, 我要这部分的数据,你准备一下,帮我放到这个篮子里面,放好了就立刻通知我。然后操作系统工厂就去协调别的事情去了,等磁盘将数据拿出来了,然后通知操作系统,你要的数据好了。 这个通知过程就是发送中断信号。

1.1.2 网络IO中断

网卡接收一些新数据过来了,网卡就会告诉操作系统,我这里刚收到了一批新数据,你赶紧过来处理一下这个数据篮子,我还要这个篮子回去收数据,这就是网卡给操作系统发送的中断消息。 然后操作系统收到这个中断消息以后就会赶紧把手上的工作停下来,去把篮子里面的东西搬出来到自己的流水线上,篮子上是有流水线地址编号的,所以搬出来的时候就知道应该放到哪条流水线上。

二. 中断类型

中断有两种类型,一种的同步中断,一种是异步中断。
在有关中断的文章和书籍中同异步中断还有很多别的名字,但都是同一个意思。内部中断/软件中断/异常都是指的同步中断,外部中断/硬件中断/中断指的都是异步中断。

2.1 同步中断(内部中断/软件中断/异常)

同步中断是指由cpu在处理过程中,自己主动发出的。 同步中断主要有两种

  1. cpu异常,在执行指定的过程中,发现指令有问题,需要执行一些中断程序,做一些相应的处理。
  2. cpu指令,指令执行过程中主动执行了中断指令,早期系统是通过中断指令实现系统调用的,后来有了专门实现的系统调用指令。
cpu异常中断类型中断程序的修复目标中断程序处理完后的动作
陷阱(trap)不需要修复执行下一条指令
故障(fault)需要修复重新执行当前指令,例如缺页故障,中断程序load了对应的页数据以后,就会重新执行这条指令
终止(abort)无法修复进程或者内核崩溃

2.2 异步中断(外部中断/硬件中断/中断)

异步中断是指cpu正常执行过程中,外部设备有一些紧急任务需要CPU进行处理,这个过程是被动要去处理这个中断。 异步中断的产生主要有两种:

  1. 外设中断,外设发过来的,需要处理的中断
  2. IPI中断,别的cpu核发过来的,需要这个核进行处理的中断

三. 中断处理

不管是软件中断还是硬件中断,都是先查询中断向量表,找到中断处理程序然后进行执行。

软件中断的处理程序是内核中已经写好的,发生软件中断的时候,根据终端向量号找到中断程序,然后暂存用户上下文,切换到内核态,直接执行中断程序就可以了。 执行期间不屏蔽别的中断信号,可能会被中断,可能会被更高优先级的进程抢占调度。

硬件中断的处理程序由于处理的是硬件相关的工作,主要要求:

  1. 需要保持最高优先级别,谁都不能抢占我的CPU,即使别的设备也发出了硬件中断的信号也不行,只有中断程序自己说我处理完了别人才能获取cpu。
  2. 需要保证执行得足够快,因为你的优先级最高,如果你执行得很慢,那别的程序就不用玩了,硬件也不用玩了。

客观现实是,硬件中断的处理可能会很复杂,有可能会耗时很久。为了解决这个问题,Linux将硬件中断的处理分成两个部分:上半部和下半部处理。 上半部处理与硬件相关的事情,大概就是,将数据拷贝出来,释放硬件的数据存储空间,这部分速度通常会很快。 下半部处理的就是对这些硬件返回来的数据的处理了,这个过程由于已经释放完硬件了,实际上主要的就是。

3.1 前提知识-中断处理器

当硬件设备需要与系统进行数据交换的时候,最快的交互方式是什么呢?当然是让他们直接连接,硬件设备直接把CPU寄存器里面的指令给替换成他们自己的指令。 但是这种直连CPU的方式的弊端就是,cpu不知道要留多少个中断针脚给外设。于是CPU直接只提供两个中断接口(可屏蔽中断引脚和不可屏蔽中断引脚),然后给 专门的设备作为中间的桥梁连接CPU的中断引脚和外设。这个设备就叫做中断控制器。
单核时代这个中断设备就叫做PIC(Promgramm Interrupt Controller)控制器。经典型号就是8259A控制器。由PIC控制器连接到CPU的中断引脚,然后其他的 外设就连接到这个中断控制器上面。由于这个PIC芯片只能连接8个外设,后来又有支持级联的PIC主板。 多核时代PIC就不够用了,于是演化成了APIC(Advance PIC)。APIC主要分两部分,每个CPU核一个local APIC,然后只有一个IO APIC,外设都连接到IO APIC, 然后再又IO APIC转发中断信号给某一个local apic中断控制前。

3.2 前提知识-中断向量

内核中有一个缓存结构-中断向量表。用来记录IRQ到中断程序的映射表,每个cpu核一份。内核和CPU共同定义了这个中断向量表记录什么,并且保存在CPU的idt寄存器上。 外设启动的时候驱动会像中断处理器申请IRQ号,然后注册在这条中断线上,外设发生中断的的时候,会根据这个中断线和IRQ找到对应的中断处理程序。 IRQ中断线 -> 中断向量表 -> 中断程序。

中断类型中断产生源中断向量号来源
同步中断cpu异常cpu架构规定,内核按照这个规定初始化的中断向量表
同步中断中断指令内核事先设定
异步中断外设中断驱动程序动态申请
异步中断IPI内核事先设定

前面32个中断是CPU预留的,自己的异常中断信号。32-255是用户自定中断信号

【查找中断处理程序ISR】
由上面中断向量表知道了,但是设备分配的都是中断信号IRQ,那是怎么找到对应的中断信号的呢?

  1. CPU异常可以在CPU内部获取
  2. 中断指令在指令里面就可以获取到向量号
  3. IPI中断呢,也是内置里面就能获取
  4. 硬件中断,驱动程序动态申请的中断线中就包含了
    1. 硬件中断的中断信号传到中断控制器,中断控制器再传给CPU
    2. CPU根据中断信号获取中断向量(32+IRQ),cpu根据idt寄存器的中断向量表找到对应向量号对应的门描述符,根据门描述符中段偏移量执行中断处理函数
    3. 在这个中断处理函数中会根据IRQ找到真正的ISR处理程序

3.3 硬件中断处理

外设中断主要分两部分,硬中断和软中断。硬中断处理与硬件相关的事情,执行硬件驱动的ISR,期间禁止中断。然后硬中断处理完以后再发起一个延后的中断处理。这个延后的中断处理 主要有四种实现:softirq, tasklet, workqueue, threaded.

硬中断处理top命令cpu中的hi中有统计, 软中断处理在top命令cpu中的si中有统计。

3.3.1 softirq

软中断在每个内核中会启一个线程ksoftirq/[num]程序,然后启动的时候驱动会注册自己的软处理函数进这个软中断子系统中。 软中断的执行时机:

  1. ksoftirq程序被调度到的时候会执行待执行的软中断程序。
  2. 硬中断抢占了软中断的执行的时候,在执行完后,会立即检查时候有pendding的软中断,有的话就唤醒直接切换到软中断,而不是重新等待cpu调度。

软中断是内核中编译的时候就已经代码确定的,通过ksoftirq程序集成调度,是静态代码的形式存在,不存在拓展的softirq类型,想增加新的软连接类型就必须 修改内核代码进行重新编译。

内核中所有的软中断类型可以在/proc/softirqs中可以看到。

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost ~]# cat /proc/softirqs
                    CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7
          HI:          1          0          0          3          3          0          0          0
       TIMER:   54642903   49053358   47249365   38240423   35899029   36858075   38457172   31446212
      NET_TX:         39         12         35         31         16         10         23         26  # 网络发包中断
      NET_RX:    1323348    1067937     909121     932855     797370     769740   36065379     664112  # 网络收包中断
       BLOCK:         49          0          0    1516737          0          0          0          0  # BLOCK层中断
BLOCK_IOPOLL:          0          0          0          0          0          0          0          0  #
     TASKLET:         81         68        101        139         68         58         96         46  # TASKLET
       SCHED:   23140516   17810726   18567044   14686090   14029759   15280671    9307016   13068328
     HRTIMER:          0          0          0          0          0          0          0          0
         RCU:    9526473    8696015    9076851    7460468    6960554    7652275    9739029    6236442

3.3.2 tasklet

tasklet是软中断的一种具体实现,这个实现的机制主要就是给softirq留了一个拓展接口。上面内核内定的软中断主要有网络收发包,磁盘BLOCK层的软中断, 时钟,调度,锁等这几类的软中断。那还有一些别的外设的软中断怎么办呢?驱动就可以实现tasklet接口,在启动期设备驱动添加自己的软中断处理仅tasklet 接口实现进tasklet链表中。执行tasklet类型的软中断的时候就会统一执行里面的tasklet链表。

tasklet与softirq的不同:

  1. tasklet运行在指定的CPU上,没有并发问题。而同一个softirq可以在不同的CPU上并发执行。

3.3.3 threaded_irq

上面看出,为了能合理处理与硬件设备的交互。系统将中断切分成了,硬中断和软中断两种方式。但是软中断的优先级也是很高的,不能调用阻塞函数,休眠等耗时 函数,因为软中断的优先级很高,所以不能做这种耗时的动作。但是的确是有些没那么重要的中断操作,不需要提升到软中断级别,也不需要有优先的资源抢占权的。 linux把他们放到进程的上下文去执行,和进程一样的调度方式。为此,内核开发了两种方法,thread_irq和workqueue

3.3.4 workqueue

系统中有一些默认的队列,也可以创建自己的队列,然后创建work,把work推送的到对应的队列中,内核线程就会去执行这些队列里面的work。 内核中这些线程就是kworker/[CPU编号]

3.4 中断的优先级

中断在处理过程中,由发生了中断,这时候的套娃行为是如何处理的呢? 首先中断的处理是,先发生硬中断,然后再由硬中断触发对应的软中断。软中断执行过程中能被硬中断抢占。 中断号越小优先级越高。

四. 中断绑定

中断绑定就是将不同的硬件中断请求分配到不同的CPU核心上执行,机制叫SMP IRQ Affinity,在Linxu 2.4开始支持。 因为CPU核心在执行过程中有自己的高速缓存,而在多核cpu的情况下,假如每个核心都能处理相同的IRQ,势必会发生不同核心间的高速缓存访问和读写同步,导致 性能下降。所以才有支持中断绑定机制的出现。

在网卡中,IRQ是根据队列进行分配的,单队列网卡只需要一个核心进行处理网络IO的软中断。 软中断查看:/proc/softirqs 硬中断查看:/proc/interrupts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@localhost ~]# cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7
  0:        159          0          0          0          0          0          0          0   IO-APIC-edge      timer
  1:         10          0          0          0          0          0          0          0   IO-APIC-edge      i8042
  6:          3          0          0          0          0          0          0          0   IO-APIC-edge      floppy
  8:          1         45          0          0          0          0          0          0   IO-APIC-edge      rtc0
  9:          0          0          0          0          0          0          0          0   IO-APIC-fasteoi   acpi
 10:         26          0          0          0          0          0          0          0   IO-APIC-fasteoi   ehci_hcd:usb1, uhci_hcd:usb2, virtio3
 11:       1930          0          0          0          0          0          0          0   IO-APIC-fasteoi   uhci_hcd:usb3, uhci_hcd:usb4, qxl
 12:        144          0          0          0          0          0          0          0   IO-APIC-edge      i8042
 14:        133          0          0    3048277          0          0          0          0   IO-APIC-edge      ata_piix
 15:          0          0          0          0          0          0          0          0   IO-APIC-edge      ata_piix
 24:          0          0          0          0          0          0          0          0   PCI-MSI-edge      virtio0-config
 25:          2          0          0          0          0          0   34668803          0   PCI-MSI-edge      virtio0-input.0
 26:          1          0          0          0          0          0          0        503   PCI-MSI-edge      virtio0-output.0
 27:          0          0          0          0          0          0          0          0   PCI-MSI-edge      virtio2-config
 28:       6097          0          0          0          0    1106159          0          0   PCI-MSI-edge      virtio2-req.0
 29:          0          0          0          0          0          0          0          0   PCI-MSI-edge      virtio1-config
 30:          2          0          0          0          0          0          0          0   PCI-MSI-edge      virtio1-virtqueues
 31:        344          0          0          0          0          0          0          0   PCI-MSI-edge      snd_hda_intel
NMI:          0          0          0          0          0          0          0          0   Non-maskable interrupts
LOC:  166199528  139183930  118880184  104249322   96393798   91320252   82645378   79960139   Local timer interrupts
SPU:          0          0          0          0          0          0          0          0   Spurious interrupts
PMI:          0          0          0          0          0          0          0          0   Performance monitoring interrupts
IWI:    1567330    1489958    1500022    1423495    1390930    1379354     955905    1262567   IRQ work interrupts
RTR:          0          0          0          0          0          0          0          0   APIC ICR read retries
RES:   34518720   27064135   22557250   22110729   20610455   21801527   31221528   17503838   Rescheduling interrupts
CAL:      78005      73143      48498     365707      56983       1702     121430     350474   Function call interrupts
TLB:    3273557    2750594    2371561    2355106    2234079    2058753    3562112    1909222   TLB shootdowns
TRM:          0          0          0          0          0          0          0          0   Thermal event interrupts
THR:          0          0          0          0          0          0          0          0   Threshold APIC interrupts
MCE:          0          0          0          0          0          0          0          0   Machine check exceptions
MCP:      10405      10405      10405      10405      10405      10405      10405      10405   Machine check polls
ERR:          0
MIS:          0

上面的10和11是共享的IRQ中断线的设备,共享中断线的在这里会打印出来。

1
2
# 查看当前系统支持多少个中断信号个数,第一个数字式是支持多少个信号,第二个是已经分配了多少个信号,第三个是预分配多少个信号。
dmesg |grep irqs

[root@node3 ~]# dmesg |grep irqs
[ 0.000000] NR_IRQS:327936 nr_irqs:456 0

4.1 中断平衡服务

中断平衡程序是负责周期性地将中断公平地分配到各个核心上执行的一个后台服务。

1
2
3
4
5
systemctl status irqbalance
systemctl stop irqbalance
systemctl disable irqbalance

service irqbalance stop

需要绑定中断到某个CPU的时候需要先关系这个这个中断平衡程序

4.2 亲和性绑定

改变亲和性又是要用到linux的内核映射目录/proc, 在/proc/irq文件夹中,每一个中断号都会有一个单独的文件夹。绑定亲和性需要修改的文件是 /proc/irq/[irq num]/smp_affinity。smp_affinity文件中的值是一个bitmap掩码形式的值,每一个二进制位代表一个核心。用16进行记录。

例如:在4核的cpu机器上,想将100这个中断绑定在第2,3个CPU上,相当于二进制 0110==0x06[16进制], cat "0x6" > /proc/irq/100/smp_affinity或者 cat "06" > /proc/irq/100/smp_affinity

参考

本文由作者按照 CC BY 4.0 进行授权

Linux IO栈

数据库并发的安全控制