1、概念说明
CPU 是一种硬件资源,和任何其他硬件设备一样也需要驱动和管理程序才能使用,我们可以把内核的进程调度看作是 CPU 的管理程序,用来管理和分配CPU 资源,合理安排进程抢占 CPU,并决定哪个进程该使用 CPU、哪个进程该等待。操作系统内核里的进程调度主要用来调度两类资源:进程(或线程)和中断,进程调度给不同的资源分配了不同的优先级,优先级最高的是硬件中断,其次是内核(系统)进程,最后是用户进程。每个 CPU 都维护着一个可运行队列,用来存放那些可运行的线程。线程要么在睡眠状态(blocked 正在等待 IO)要么在可运行状态,如果 CPU 当前负载太高而新的请求不断,就会出现进程调度暂时应付不过来的情况,这个时候就不得不把线程暂时放到可运行队列里。
1.1 Interrupt rate
中断简介:
中断其实就是由硬件或软件所发送的一种称为IRQ(中断请求)的信号。 中断允许让设备,如键盘,串口卡,并口等设备表明它们需要CPU。一旦CPU接收了中断请求,CPU就会暂时停止执行正在运行的程序,并且调用一个称为中断处理器或中断服务程序(interrupt service routine)的特定程序。中断服务程序或中断处理器可以在中断向量表中找到,而这个中断向量表位于内存中的固定地址中。中断被CPU处理后,就会恢复执行之前被中断的程序。其实,在机器启动的时候,系统就已经识别了所有设备,并且也把相应的中断处理器加载到中断表中。
Interrupt rate:
每秒内的设备中断数。CPU接收硬件驱动发出的中断请求数。当一个驱动器有一个时间需要被kernel操作时。例如:如果一个磁盘控制器从磁盘上取得了一个数据块,kernel需要读取使用这个块,那么磁盘控制器会触发一个中断; kernel接收每个中断。
在系统中,中断处理器的优先级非常高,而且执行速度非常快。很多时候,有些中断处理并不需要很高的处理优先级,所以也有soft-interrupt handler。如果有很多的中断,kernel需要花费大量的时间去处理中断。可以检查/proc/interrupts能够知道中断发生在哪个CPU 上。
Interrupt Rate中包括内核由于进程的时间片中断,内核的时钟频率可以通过如下命令知道,每秒总的时钟中断数就是 = cpu个数 * 核数 * CONFIG_HZ
cat /boot/config-`uname -r` | grep '^CONFIG_HZ='
CONFIG_HZ=100
通过cat /proc/interrupts可以查看中断的类型以及次数,用vmstat查看的 in(Interrupt)就是这个参数
1.2 Context Switch Rate
大部分现在的CPU在同一时间只能运行一个process,虽然也有一些CPU,例如超线程技术的CPU,能实现同时运行超过一个process。linux把这种CPU看作多个单线程CPU,linux内核不断的在不同process间切换,造成一个错觉,让人感觉一个单CPU同时处理多个任务,不同process之间的切换称作 Context Switch。
当系统做Context Switch时,CPU保存所有old process的context信息并获得new process的所有context信息,Context信息包括大量的linux追踪每个process信息,尤其是一些资源: 那些process正在执行,被分配了哪些内存,它打开了那些文件,等等。切换Context会触发大量的信息移动,这是比较高的开销。
首先,kernel调度触发context switches。为了保证每个process平等的共享CPU时间,kernel周期性中断running的process,如果合适kernel调度器会开始一个其他的process而不是让当前的process继续执行,每次的周期性中断或者定时中断都可能触发context switch。每秒定时中断的次数因不同架构和不同的kernel版本而不同。
获取每秒中断次数的一个简单办法是通过监控 /proc/interrupts文件,通过命令cat /proc/interrupts | grep timer; sleep 10 ; cat /proc/interrupts | grep timer 可以看到在指定的时间内timer次数的变化。如果你的context switch比timer中断大很多。那么context switch更多的可能是I/O请求或者其他长时间的系统调用(比如sleep)产生。
当一个应用请求一个操作不能立即实现时,kernel开始 context switch操作: 存入请求的process并且试着切换到其他runnable process。这将使得CPU保持工作状态。Context Switch大体上由两个部分组成:中断和进程(包括线程)切换,一次中断(Interrupt)会引起一次切换,进程(线程)的创建、激活之类的也会引起一次切换。 Context Switch 的值也和TPS(Transaction Per Second)相关的,假设每次调用会引起N次CS,那么就可以得出
Context Switch Rate = Interrupt Rate + TPS* N
CSR减掉IR,就是进程/线程的切换,假如主进程收到请求交给线程处理,线程处理完毕归还给主进程,这里就是2次切换。也可以用CSR、IR、TPS的值代入公式中,得出每次事物导致的切换数。因此,要降低CSR,就必须在每个TPS引起的切换上下功夫,只有N这个值降下去,CSR就能降低,理想情况下N=0,但是无论如何如果N >= 4,则要分析。用vmstat查看 cs(Context Switch)就是这个参数
2、CPU中断过程
2.1 cpu中断技术的定义
- 计算机处于执行期间
- 系统内发生了非寻常或非预期的急需处理事件
- CPU暂时中断当前正在执行的程序而转去执行相应的事件处理程序
- 处理完毕后返回原来被中断处继续执行
举个现实中的例子:
你正在看书,突然你的朋友打来电话,于是你放下书本去接电话,电话打完接着看书。电话响->放下书本->接电话->继续看书这一个过程,就类似于CPU中断的处理过程。
2.2 CPU中断的作用
早期的CPU处理外设的事件(比如接收键盘输入),往往采用“轮询”的方式。即CPU像个查岗的一样轮番对外设顺序访问,比如它先看看键盘有没被按下,有的话就处理,没的话继续往下看鼠标有没有移动,再看看打印机……这种方式使CPU的执行效率很低,且CPU与外设不能同时工作(因为要等待CPU来“巡查”)。中断模式时就是说CPU不主动访问这些设备,只管处理自己的任务。如果有设备要与CPU联系,或要CPU处理一些事情,它会给CPU发一个中断请求信号。这时CPU就会放下正在进行的工作而去处理这个外设的请求。处理完中断后,CPU返回去继续执行中断以前的工作。
中断模式的作用和优点
- 可以使CPU和外设同时工作,使系统可以及时地响应外部事件,外设的处理速度一般慢于cpu,cpu不能一直等待外部事件
- 可允许多个外设同时工作,大大提高了CPU的利用率,也提高了数据输入、输出的速度。
- 可以使CPU及时处理各种软硬件故障(比如计算机在运行过程中,出现了难以预料的情况或一些故障,如电源掉电、存储出错、运算溢出等等。计算机可以利用中断系统自行处理,而不必停机或报告工作人员。)
2.3 CPU中断的类型
在计算机系统中,根据中断源的不同,通常将分为硬件中断和软件中断
(1)硬件中断
硬件中断又称外部中断,分为:可屏蔽中断、非屏蔽中断。
可屏蔽中断。常由计算机的外设或一些接口功能产生,如键盘、打印机、串行口等;这种类型的中断可以在CPU要处理其它紧急操作时,被软件屏蔽或忽略。例如打印机中断,CPU对打印机中断请求的响应可以快一些,也可以慢一些,因为让打印机稍等待一会也是完全合理的。
非屏蔽中断。由意外事件导致,如电源断电、内存校验错误等;对于这种类型的中断事件,无法通过软件进行屏蔽,CPU必须无条件响应。例如电源断电,一旦出现此中断请求,必须立即无条件地响应,否则进行其他任何工作都是没有意义的。
在x86架构的处理器中,CPU的中断控制器由两根引脚(INTR和NMI)接收外部中断请求信号。其中: 1. INTR接收可屏蔽中断请求;2. NMI接收非屏蔽中断请求
(2)软件中断
软件中断又称内部中断、异常,是指在程序中调用INTR中断指令引起的中断。比如winAPI中,keybd_event和mouse_event两个函数,就是用来模拟键盘和鼠标的输入(只是猜测)。
2.4 CPU中断的过程
(1)中断请求
中断请求是由中断源向CPU发出中断请求信号。外部设备发出中断请求信号要具备以下条件:外部设备的工作已经告一段落。例如输入设备只有在启动后,将要输入的数据送到接口电路的数据寄存器(即准备好要输入的数据)之后,才可以向CPU发出中断请求;系统允许该外设发出中断请求。如果系统不允许该外设发出中断请求,可以将这个外设的请求屏蔽。当这个外设中断请求被屏蔽,虽然这个外设准备工作已经完成,也不能发出中断请求。
(2)中断响应、处理和返回
当满足了中断的条件后,CPU就会响应中断,转入中断程序处理。关闭中断信号接收器;保存现场(context);给出中断入口,转入相应的中断服务程序;处理完成,返回并恢复现场(context);开启中断信号接收器
(3)中断排队和中断判优
中断申请是随机的,有时会出现多个中断源同时提出中断申请。CPU每次只能响应一个中断源的请求。CPU不可能对所有中断请求一视同仁,它会根据各中断源工作性质的轻重缓急,预先安排一个优先级顺序。当多个中断源同时申请中断时,即按此优先级顺序进行排队,等候CPU处理。
3、中断处理机制
Linux中断下半部处理有三种方式:软中断、tasklet、工作队列。
3.1 下半部
中断处理函数所做的第一件事情就是屏蔽中断(或者是什么都不做,因为常常是如果不清除IF位,就等于屏蔽中断了),当然只屏蔽同一种中断。之所以要屏蔽中断,是因为新的中断会再次调用中断处理函数,导致原来中断处理现场的破坏。即破坏了interrupt context。
随着系统的不断复杂,中断处理函数要做的事情也越来越多,多到都来不及接收新的中断了。于是发生了中断丢失,这显然不行,于是产生了新的机制:分离中断接收与中断处理过程。
系统为了解决中断程序执行过长和中断丢失的问题,Linux将中断处理过程分成了两个阶段,也就是上半部和下半部
Linux中断分为两个半部:上半部(tophalf)和下半部(bottom half)。上半部的功能是"登记中断",当一个中断发生时,它进行相应地硬件读写后就把中断例程的下半部挂到该设备的下半部执行队列中去。因此,上半部 执行的速度就会很快,可以服务更多的中断请求。但是,仅有"登记中断"是远远不够的,因为中断的事件可能很复杂。因此,Linux引入了一个下半部,来完 成中断事件的绝大多数使命。下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的,下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断!下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。总之:
- 1.上半部用来快速处理中断,它在中断禁止模式下运行,主要处理跟硬件紧密相关或者时间敏感的工作
- 2.下半部用来延迟处理上半部未完成的工作,通常以内核线程的方式运行
3.2 软中断
linux中断处理。作为一个操作系统显然不能任由每个中断都各自为政,统一管理是必须的。不可中断部分的共同部分放在函数do_IRQ中,需要添加中断处理函数时,通过request_irq实现。下半部放在do_softirq中,也就是软中断,通过open_softirq添加对应的处理函数。
3.3 Tasklet
随着中断数的不停增加,软中断不够用了,于是下半部又做了进化。软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。显然当年开发人员为了保证轮询的效率,于是限制中断个数为32个。为了提高中断处理数量,顺道改进处理效率,于是产生了tasklet机制。
Tasklet采用无差别的队列机制,有中断时才执行,免去了循环查表之苦。Tasklet作为一种新机制,显然可以承担更多的优点。正好这时候SMP越来越火了,因此又在tasklet中加入了SMP机制,保证同种中断只在一个cpu上执行。在软中断时代,显然没有这种考虑。因此同一种中断可以在两个cpu上同时执行,很可能造成冲突。tasklet的优点无类型数量限制;效率高无需循环查表;支持SMP机制;
3.4工作队列
前面的机制不论如何折腾,有一点是不会变的。它们都在中断上下文中。即它们不可挂起。而且由于是串行执行,因此只要有一个处理时间较长,则会导致其他中断响应的延迟。为了完成这些不可能完成的任务,于是出现了工作队列。工作队列说白了就是一组内核线程,作为中断守护线程来使用。多个中断可以放在一个线程中,也可以每个中断分配一个线程。工作队列对线程作了封装,使用起来更方便。因为工作队列是线程,所以我们可以使用所有可以在线程中使用的方法。
Tasklet其实也不一定是在中断上下文中执行,它也有可能在线程中执行。假如中断数量很多,而且这些中断都是自启动型的(中断处理函数会导致新的中断产生),则有可能cpu一直在这里执行中断处理函数,会导致用户进程永远得不到调度时间。为了避免这种情况,linux发现中断数量过多时,会把多余的中断处理放到一个单独的线程中去做,就是ksoftirqd线程。这样又保证了中断不多时的响应速度,又保证了中断过多时不会把用户进程饿死。
问题是我们不能保证我们的tasklet或软中断处理函数一定会在线程中执行,所以还是不能使用进程才能用的一些方法,如放弃调度、长延时等。
Linux实现下半部的机制主要有tasklet和工作队列
示例:
以网卡接收数据为例
网卡接收到数据包后,会通过硬件中断的方式,通知内核有新的数据到了,这时内核就应该调用中断处理程序来响应它;
对于上半部分来说,要把网卡的数据读到内存中,然后更新硬件寄存器的状态(表示数据已经读好了),最后再发送一个软中断信号,通知下半部做进一步的处理
下半部被软中断信号唤醒后,需要从内存中找到网络数据,再按照网络协议栈,对数据进行逐层解析和处理,直到把它送给应用程序
上半部分直接处理硬件请求,也就是硬件中断,特点是快速执行
下半部分则是由内核处理,也就是软中断,特点是延迟执行
上半部会打断CPU正在执行的人物,然后立即执行中断处理程序,而下半部以内核线程的方式执行,并且每个CPU都对应一个软中断内核线程,名字为 ksoftireqd/CPU编号
比如0号CPU对应的软中断内核线程名字是 ksoftirqd/0
一些内核自定义的事件也属于软中断,比如内核调度和RCU锁(Read-Copy Update的缩写,是Linux内核中最常用的锁之一)
4. 指标范围
4.1 Interrupt rate
应该与cpu利用率结合分析,如果cpu利用率在合理范围内,大量的中断也是可以接受的。一个巨大的中断值,同时伴随着缓慢的系统性能表现,指示存在硬件问题
4.2 Context Switch Rate
应该与cpu利用率结合分析,如果cpu利用率在合理范围内,大量的中断也是可以接受的。
当每次调用引起的上下文切换大于等于4时,需要结合分析
网上也有说,需要少于5000*cpu个数