中断
1. 指标范围
1.1 Interrupt rate
应该与cpu利用率结合分析,如果cpu利用率在合理范围内,大量的中断也是可以接受的。一个巨大的中断值,同时伴随着缓慢的系统性能表现,指示存在硬件问题
1.2 Context Switch Rate
应该与cpu利用率结合分析,如果cpu利用率在合理范围内,大量的中断也是可以接受的。
当每次调用引起的上下文切换大于等于4时,需要结合分析
网上也有说,需要少于5000*cpu个数
2. 概念说明
CPU 是一种硬件资源,和任何其他硬件设备一样也需要驱动和管理程序才能使用,我们可以把内核的进程调度看作是 CPU 的管理程序,用来管理和分配CPU 资源,合理安排进程抢占 CPU,并决定哪个进程该使用 CPU、哪个进程该等待。操作系统内核里的进程调度主要用来调度两类资源:进程(或线程)和中断,进程调度给不同的资源分配了不同的优先级,优先级最高的是硬件中断,其次是内核(系统)进程,最后是用户进程。每个 CPU 都维护着一个可运行队列,用来存放那些可运行的线程。线程要么在睡眠状态(blocked 正在等待 IO)要么在可运行状态,如果 CPU 当前负载太高而新的请求不断,就会出现进程调度暂时应付不过来的情况,这个时候就不得不把线程暂时放到可运行队列里。
2.1 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)就是这个参数
2.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)就是这个参数
3. 浅析cpu中断过程
此处可直接阅读http://www.cnblogs.com/funeral/archive/2013/03/06/2945485.html
3.1 cpu中断技术的定义
ü 计算机处于执行期间
ü 系统内发生了非寻常或非预期的急需处理事件
ü CPU暂时中断当前正在执行的程序而转去执行相应的事件处理程序
ü 处理完毕后返回原来被中断处继续执行
举个现实中的例子:
你正在看书,突然你的朋友打来电话,于是你放下书本去接电话,电话打完接着看书。电话响->放下书本->接电话->继续看书这一个过程,就类似于CPU中断的处理过程。
3.2 CPU中断的作用
早期的CPU处理外设的事件(比如接收键盘输入),往往采用“轮询”的方式。即CPU像个查岗的一样轮番对外设顺序访问,比如它先看看键盘有没被按下,有的话就处理,没的话继续往下看鼠标有没有移动,再看看打印机……这种方式使CPU的执行效率很低,且CPU与外设不能同时工作(因为要等待CPU来“巡查”)。中断模式时就是说CPU不主动访问这些设备,只管处理自己的任务。如果有设备要与CPU联系,或要CPU处理一些事情,它会给CPU发一个中断请求信号。这时CPU就会放下正在进行的工作而去处理这个外设的请求。处理完中断后,CPU返回去继续执行中断以前的工作。
中断模式的作用和优点
ü 可以使CPU和外设同时工作,使系统可以及时地响应外部事件,外设的处理速度一般慢于cpu,cpu不能一直等待外部事件
ü 可允许多个外设同时工作,大大提高了CPU的利用率,也提高了数据输入、输出的速度。
ü 可以使CPU及时处理各种软硬件故障(比如计算机在运行过程中,出现了难以预料的情况或一些故障,如电源掉电、存储出错、运算溢出等等。计算机可以利用中断系统自行处理,而不必停机或报告工作人员。)
3.3 CPU中断的类型
在计算机系统中,根据中断源的不同,通常将分为硬件中断和软件中断
ü 硬件中断
硬件中断又称外部中断,分为:可屏蔽中断、非屏蔽中断。
可屏蔽中断。常由计算机的外设或一些接口功能产生,如键盘、打印机、串行口等;这种类型的中断可以在CPU要处理其它紧急操作时,被软件屏蔽或忽略。例如打印机中断,CPU对打印机中断请求的响应可以快一些,也可以慢一些,因为让打印机稍等待一会也是完全合理的。
非屏蔽中断。由意外事件导致,如电源断电、内存校验错误等;对于这种类型的中断事件,无法通过软件进行屏蔽,CPU必须无条件响应。例如电源断电,一旦出现此中断请求,必须立即无条件地响应,否则进行其他任何工作都是没有意义的。
在x86架构的处理器中,CPU的中断控制器由两根引脚(INTR和NMI)接收外部中断请求信号。其中: 1. INTR接收可屏蔽中断请求;2. NMI接收非屏蔽中断请求
ü 软件中断
软件中断又称内部中断、异常,是指在程序中调用INTR中断指令引起的中断。比如winAPI中,keybd_event和mouse_event两个函数,就是用来模拟键盘和鼠标的输入(只是猜测)。
3.4 CPU中断的过程
ü 中断请求
中断请求是由中断源向CPU发出中断请求信号。外部设备发出中断请求信号要具备以下条件:外部设备的工作已经告一段落。例如输入设备只有在启动后,将要输入的数据送到接口电路的数据寄存器(即准备好要输入的数据)之后,才可以向CPU发出中断请求;系统允许该外设发出中断请求。如果系统不允许该外设发出中断请求,可以将这个外设的请求屏蔽。当这个外设中断请求被屏蔽,虽然这个外设准备工作已经完成,也不能发出中断请求。
ü 中断响应、处理和返回
当满足了中断的条件后,CPU就会响应中断,转入中断程序处理。关闭中断信号接收器;保存现场(context);给出中断入口,转入相应的中断服务程序;处理完成,返回并恢复现场(context);开启中断信号接收器
ü 中断排队和中断判优
中断申请是随机的,有时会出现多个中断源同时提出中断申请。CPU每次只能响应一个中断源的请求。CPU不可能对所有中断请求一视同仁,它会根据各中断源工作性质的轻重缓急,预先安排一个优先级顺序。当多个中断源同时申请中断时,即按此优先级顺序进行排队,等候CPU处理。
3.5 多核CPU对中断的处理
多核CPU的中断处理和单核有很大不同。多核的各处理器核心之间需要通过中断方式进行通信,所以CPU芯片内部既有各处理器核心的本地中断控制器,又有负责仲裁各核之间中断分配的全局中断控制器。现今的多核处理器在中断处理和中断控制方面主要使用的是APIC(Advanced Programmable Interrupt Controllers),即高级编程中断控制器。它是基于中断控制器两个基础功能单元--本地单元以及I/O单元的分布式体系结构。在多核系统中,多个本地和I/O APIC单元能够作为一个整体通过APIC总线互相操作。
APIC的功能有,接受来自处理器中断引脚的内部或外部I/O APIC的中断,然后将这些中断发送给处理器核心进行处理。在多核处理器系统中,接收和发送核内中断消息
对于外部设备发出的中断请求,由全局中断控制器接收请求并决定交给CPU的哪一个核心处理。也可针对APIC编程,让所有的中断都被一个固定的CPU处理。
4. Linux中的中断处理
本章节文章可直接阅读http://www.uml.org.cn/embeded/201304021.asp
4.1 中断实现
在Linux驱动程序中,为设备实现一个中断包含两个步骤:向内核注册中断;实现中断处理函数。
在实地址模式中,CPU把内存中从0开始的1KB空间作为一个中断向量表。表中的每一项占4个字节。但是在保护模式中,有这4个字节的表项构成的中断向量表不满足实际需求,于是根据反映模式切换的信息和偏移量的足够使得中断向量表的表项由8个字节组成,而中断向量表也叫做了中断描述符表(IDT)。在CPU中增加了一个用来描述中断描述符表寄存器(IDTR),用来保存中断描述符表的起始地址。
外部设备当需要操作系统做相关的事情的时候,会产生相应的中断。设备通过相应的中断线向中断控制器发送高电平以产生中断信号,而操作系统则会从中断控制器的状态位取得那根中断线上产生的中断。而且只有在设备在对某一条中断线拥有控制权,才可以向这条中断线上发送信号。也由于现在的外设越来越多,中断线又是很宝贵的资源不可能被一一对应。因此在使用中断线前,就得对相应的中断线进行申请。无论采用共享中断方式还是独占一个中断,申请过程都是先将所有的中断线进行扫描,得出哪些没有别占用,从其中选择一个作为该设备的IRQ。其次,通过中断申请函数申请相应的IRQ。最后,根据申请结果查看中断是否能够被执行
4.2 中断处理机制
Linux中断下半部处理有三种方式:软中断、tasklet、工作队列。
4.2.1 最简单的中断机制
最简单的中断机制就是像芯片手册上讲的那样,在中断向量表中填入跳转到对应处理函数的指令,然后在处理函数中实现需要的功能,它的好处很明显,简单,直接。类似下图:
4.2.2 下半部
中断处理函数所做的第一件事情就是屏蔽中断(或者是什么都不做,因为常常是如果不清除IF位,就等于屏蔽中断了),当然只屏蔽同一种中断。之所以要屏蔽中断,是因为新的中断会再次调用中断处理函数,导致原来中断处理现场的破坏。即破坏了interrupt context。
随着系统的不断复杂,中断处理函数要做的事情也越来越多,多到都来不及接收新的中断了。于是发生了中断丢失,这显然不行,于是产生了新的机制:分离中断接收与中断处理过程。
Linux中断分为两个半部:上半部(tophalf)和下半部(bottom half)。上半部的功能是"登记中断",当一个中断发生时,它进行相应地硬件读写后就把中断例程的下半部挂到该设备的下半部执行队列中去。因此,上半部 执行的速度就会很快,可以服务更多的中断请求。但是,仅有"登记中断"是远远不够的,因为中断的事件可能很复杂。因此,Linux引入了一个下半部,来完 成中断事件的绝大多数使命。下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的,下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断!下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。
从上图中看,只看int0的处理。Func0为中断接收函数。中断只能简单的触发func0,而func0则能做更多的事情,它与funcA之间可以使用队列等缓存机制。当又有中断发生时,func0被触发,然后发送一个中断请求到缓存队列,然后让funcA去处理。由于func0做的事情是很简单的,所以不会影响int0的再次接收。而且在func0返回时就会使能int0,因此funcA执行时间再长也不会影响int0的接收。
4.2.3 软中断
linux中断处理。作为一个操作系统显然不能任由每个中断都各自为政,统一管理是必须的。不可中断部分的共同部分放在函数do_IRQ中,需要添加中断处理函数时,通过request_irq实现。下半部放在do_softirq中,也就是软中断,通过open_softirq添加对应的处理函数。
4.2.4 Tasklet
随着中断数的不停增加,软中断不够用了,于是下半部又做了进化。软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。显然当年开发人员为了保证轮询的效率,于是限制中断个数为32个。为了提高中断处理数量,顺道改进处理效率,于是产生了tasklet机制。
Tasklet采用无差别的队列机制,有中断时才执行,免去了循环查表之苦。Tasklet作为一种新机制,显然可以承担更多的优点。正好这时候SMP越来越火了,因此又在tasklet中加入了SMP机制,保证同种中断只在一个cpu上执行。在软中断时代,显然没有这种考虑。因此同一种中断可以在两个cpu上同时执行,很可能造成冲突。tasklet的优点无类型数量限制;效率高无需循环查表;支持SMP机制;
4.2.5 工作队列
前面的机制不论如何折腾,有一点是不会变的。它们都在中断上下文中。即它们不可挂起。而且由于是串行执行,因此只要有一个处理时间较长,则会导致其他中断响应的延迟。为了完成这些不可能完成的任务,于是出现了工作队列。工作队列说白了就是一组内核线程,作为中断守护线程来使用。多个中断可以放在一个线程中,也可以每个中断分配一个线程。工作队列对线程作了封装,使用起来更方便。因为工作队列是线程,所以我们可以使用所有可以在线程中使用的方法。
Tasklet其实也不一定是在中断上下文中执行,它也有可能在线程中执行。假如中断数量很多,而且这些中断都是自启动型的(中断处理函数会导致新的中断产生),则有可能cpu一直在这里执行中断处理函数,会导致用户进程永远得不到调度时间。为了避免这种情况,linux发现中断数量过多时,会把多余的中断处理放到一个单独的线程中去做,就是ksoftirqd线程。这样又保证了中断不多时的响应速度,又保证了中断过多时不会把用户进程饿死。
问题是我们不能保证我们的tasklet或软中断处理函数一定会在线程中执行,所以还是不能使用进程才能用的一些方法,如放弃调度、长延时等。
Linux实现下半部的机制主要有tasklet和工作队列
4.3 中断函数
与Linux设备驱动中中断处理相关的首先是申请与释放IRQ的API request_irq()和free_irq()
4.3.1 中断注册函数
用于实现中断的注册功能的request_irq,int request_irq(unsigned int irq, void (*handler)(int, void*, struct pt_regs *), unsigned long irqflags, const char *devname, void *dev_id)返回0表示成功,或者返回一个错误码
参数说明:
ü unsigned int irq //要申请的硬件中断号
ü void (*handler)(int,void *,struct pt_regs *) //是向系统登记的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数
ü unsigned long irqflags //与中断管理有关的属性,若设置SA_INTERRUPT,标明中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程 序不屏蔽;若设置SA_SHIRQ,则多个设备共享中断。
ü const char * devname //设备名
ü void *dev_id 共享中断时使用,用作共享中断线的指针。一般设置为这个设备的device结构本身或者NULL。它是一个独特的标识,用在当释放中断线时以及可能还被驱动用来指向它自己的私有数据区,来标识哪个设备在中断;这个参数在真正的驱动程序中一般是指向设备数据结构的指针。在调用中断处理程序的时候它就会传递给中断处理程序的void *dev_id。如果中断没有被共享dev_id 可以设置为 NULL。
flags参数与中断管理有关的选项说明
#define IRQF_DISABLED 0x00000020 /*中断禁止*/
#define IRQF_SAMPLE_RANDOM 0x00000040 /*供系统产生随机数使用*/
#define IRQF_SHARED 0x00000080 /*在设备之间可共享*/
#define IRQF_PROBE_SHARED 0x00000100/*探测共享中断*/
#define IRQF_TIMER 0x00000200/*专用于时钟中断*/
#define IRQF_PERCPU 0x00000400/*每CPU周期执行中断*/
#define IRQF_NOBALANCING 0x00000800/*复位中断*/
#define IRQF_IRQPOLL 0x00001000/*共享中断中根据注册时间判断*/
#define IRQF_ONESHOT 0x00002000/*硬件中断处理完后触发*/
#define IRQF_TRIGGER_NONE 0x00000000/*无触发中断*/
#define IRQF_TRIGGER_RISING 0x00000001/*指定中断触发类型:上升沿有效*/
#define IRQF_TRIGGER_FALLING 0x00000002/*中断触发类型:下降沿有效*/
#define IRQF_TRIGGER_HIGH 0x00000004/*指定中断触发类型:高电平有效*/
#define IRQF_TRIGGER_LOW 0x00000008/*指定中断触发类型:低电平有效*/
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH|IRQF_TRIGGER_LOW|\IRQF_TRIGGER_RISING| IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010/*触发式检测中断*/
说明:如果设置IRQF_DISABLED(SA_INTERRUPT),表示是一个“快速”中断处理程序否则是一个“慢速”中断处理程序。快速中断保证中断处理的原子性(不被打断),而慢速中断则不保证。即“开启中断”标志位(处理器IF)在运行快速中断处理程序时是关闭的,因此在服务该中断时,不会被其他类型的中断打断;而调用慢速中断处理时,其它类型的中断仍可以得到服务。
ü IRQF_SHARED(SA_SHIRQ)
该位表明中断可以在设备间共享。共享中断就是将不同的设备挂到同一个中断信号线上。Linux对共享的支持主要是为PCI设备服务。共享中断也是通过request_irq函数来注册的,但有三个特别之处:申请共享中断时,必须在flags参数中指定 IRQF_SHARED位;dev_id参数必须是唯一的;共享中断的处理程序中,不能使用disable_irq(unsigned int irq),如果使用了这个函数,共享中断信号线的其它设备将同样无法使用中断,也就无法正常工作了
4.3.2 中断处理函数
中断处理程序就是普通的C代码,是在中断上下文中运行的,它的行为受到某些限制:不能向用户空间发送或接受数据;不能使用可能引起阻塞的函数;不能使用可能引起调度的函数。
void short_sh_interrupt(int irq, void *dev_id, struct pt_regs *regs)//判断是否是本设备产生了中断
{value = inb(short_base);
if (!(value & 0x80)) return;//清除中断位(如果设备支持自动清除,则不需要这步)
outb(value & 0x7F, short_base);//中断处理,通常是数据接收
。。。。。。。。。/* 唤醒等待数据的进程 */
wake_up_interruptible(&short_queue);
4.3.3 释放中断
当设备不再需要使用中断时(通常在驱动卸载时),应当把它们返还给系统,使用函数void free_irq(unsigned int irq, void *dev_id)
5. 参考资料
http://www.cnblogs.com/funeral/archive/2013/03/06/2945485.html
http://www.uml.org.cn/embeded/201304021.asp
http://blog.chinaunix.net/uid-27139412-id-3955865.html
http://blog.163.com/leichi3255@126/blog/static/748333872010047527968/
http://blog.csdn.net/stanjiang2010/article/details/5760743