Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2665
  • 博文数量: 3
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 50
  • 用 户 组: 普通用户
  • 注册时间: 2013-07-11 19:28
文章分类
文章存档

2013年(3)

我的朋友
最近访客

分类: 其他平台

2013-09-10 17:18:03


1介绍

我们知道,处理器的处理速度比硬件来说要快上N个数量级,那么由处理器向硬件发出请求并等待回应的办法显然是不可取的,在这期间处理器浪费了大量的时间。这些时间应该被用来处理其他的事务。轮询可能是解决办法之一,但显然这样的办法也会让处理器做大量的无用功。

最好的办法,就是让硬件在需要的时候才向内核发出信号,然后处理器去响应硬件的请求。这就是中断机制。

1.1什么是中断

当硬件需要和处理器时,会产生一个电信号(即中断信号),并发往处理器,处理器收到后,会告诉OS,然后由OS进行后续处理。

每一种硬件设备都有其专门的中断值,OS得以通过这个来进行区分,到底是哪个设备发生了中断。这些中断值又被称为中断请求(IRQ)

当然有的IRQ是动态分配的。其实OS关注的并不是某个设备一定要产生某个特定的IRQ,而是一个特定的IRQ要和一个特定的设备有映射的关系,而且OS需要知道这种关系。这就表明,即便是IRQ在多个设备之间进行共享也是可以的,只要OS能够知道这些映射关系,并且能够有办法区分某一个时刻产生中断的设备是哪一个即可。

1.2中断处理程序

中断处理程序,顾名思义,自然就是发生中断时,需要调用的处理函数。

特点:1、不是每设备一个处理程序,而是每中断一个处理程序[微软用户1]。

2、运行于特殊的上下文中:中断上下文

3、一般的中断处理程序,都会关中断,考虑到中断随时都有可能发生,处理程序应当尽可能的高效

4、一般中断处理程序都是要和硬件打交道的

1.3中断上下文1.3.1回忆进程上下文

1、对内核而言,处于进程上下文表明内核处于这样一种模式:进程在执行(系统调用或者运行内核线程)

2、可以通过current宏关联当前进程

3、进程以进程上下文的形式关联内核,使得在进程上下文中可以睡眠,也可以调用调度程序。

1.3.2中断上下文

中断上下文,则和进程上下文几乎完全相反:和进程无关、和current宏无关à不能睡眠、不能调用会导致睡眠的函数。

另外,处于中断上下文的代码,应当迅速、简洁,尽量把工作放到下半部中去完成。

关于中断使用的堆栈:2.6的内核之前,中断没有自己的堆栈,而是与被中断的内核线程共享该线程的堆栈(2页)。2.6之后,内核增加了一个选项:每个内核线程只提供一页内存,这减轻了内存的压力,也同时促使中断被分离了出来:每处理器一页,称为中断栈

1.4中断的实现机制

Linux中,中断的处理机制是依赖于体系结构的(处理器、中断控制器、体系结构的设计、机器本身)。下图是中断的路由

650) this.width=650;" src="" title="11.png" />

1.5关于中断的下半部

我们为什么需要下半部?因为我们要把中断处理中需要做的工作区分开来:中断处理程序中,只处理那些有严格时间限制的工作,比如复位硬件,对中断进行应答等。而那些可以拖到后面做的,或者说有可能睡眠的处理,都应当放到下半部去处理

这样做的目的很显然,就是让中断处理程序尽可能的简洁明快

在适当的时机,下半部会开中断执行

2中断控制

为什么要控制中断?控制中断的原因,说到底还是为了要进行同步。

通过禁止中断,可以确保该中断不会抢占当前运行的代码。禁止中断还可以禁止内核抢占。

需要注意的是,中断都是对每处理器而言(中断堆栈),也就是说,禁止中断并不能够保证自己使用的数据不会被其他处理器的并发进程所访问到。因此如果在使用某些全局的数据时,需要考虑对其进行加锁保护。

即:锁提供机制,防止来自其他处理器(当然也可以是本处理器)的并发访问,中断提供机制,防止来自其他中断处理程序的并发访问。

2.1禁止和激活中断

这里需要注意的是,内核提供两类接口:禁止/激活中断,保存/恢复中断状态。前者比较傻瓜,会无条件的禁止/激活中断,这需要使用者对当前的中断状态十分确定。而后者则相对更容易使用,免去了判断

另外关于cli()与sti(): 在2.5版本之前的内核,提供“禁止所有处理器上的中断”这样的功能,现在已经去掉了,需要开发人员用锁来避免并发。这么做的好处是,是的代码更加流线化,不会簇拥成团。而且使用粒度更细的锁,会比全局锁要效率高。

2.2禁止指定中断线

我们当然不用禁止全局的中断,有时候禁止某一条中断线就可以了,这是指:凡是在该中断线上产生的中断,都将不会报告给处理器。

一般对于有多个中断处理程序共享的中断线,并不建议使用这个功能,因为这会导致这条线上的其他的设备无法传递中断。

2.3中断系统的状态

有时候我们需要判断代码所处的状态:是否在中断上下文中。因为有些操作是只有在进程上下文中才能够进行的,比如睡眠。

系统提供接口,来判断中断是否被禁止、是否处于中断上下文、是否正在执行中断处理程序。

3下半部

一般的中断处理都会分为两个部分,前面讲到的中断处理程序只是所谓的上半部,这是系统处理中断不可或缺的一部分。但是由于中断处理程序本身的局限,仅靠上半部是无法高效的处理系统的所有中断的,这就需要下半部来提供支持。

3.1为什么需要下半部3.1.1上半部的局限

1、中断处理程序异步执行,且有可能打断其他代码,包括其他的中断处理程序

2、中断处理程序执行时,会禁止该中断同级的其他中断,甚至禁止全局所有中断

3、往往需要对硬件进行操作

4、中断处理程序在中断上下文中运行,有很多限制

以上几点,就要求中断处理程序不仅要简洁、高效,而且对于阻塞这种行为也是不能支持的,这就导致上半部的限制很多。

3.1.2使用下半部

1、处理中断时,工作推后到下半部的原因,就是为了突破上面提到的局限性,要尽可能的将那些可以推后执行的工作都放到下半部,提高系统的响应速度

2、下半部何时被调用?这个跟中断处理选择用何种下半部机制有关,可以是推后一段时间,也可以是通过定时器

3、下半部的特点:在进行处理的时候,随时都有可能响应中断

4、除了Linux,很多其他的操作系统也采用了同样的机制

3.2下半部介绍

1、下半部的实现方式,在2.6的内核中,有3种:软中断、tasklet、工作队列。软中断用的比较少,只有有限的几种使用场景,更多的是使用tasklet

2、内核定时器,同样也能够将工作推后一段时间(精确的)进行

3.3软中断3.3.1软中断的实现

1、软中断是在内核编译期间静态分配的,不能够动态裁剪。系统定义了一个32个元素的数组,但是目前只用到了寥寥几个。多数还是通过tasklet来实现

2、软中断不会抢占相同处理器上的其他软中断,软中断会被中断给打断

3、软中断的执行:中断处理程序在返回之前,将对应的软中断进行标记(触发软中断),然后系统会在合适的时机检查该标记,并执行软中断(从中断处理程序返回时、在ksoftirqd内核线程中、显示检查与处理软中断的代码)。

3.3.2软中断的使用

1、软中断是给系统中对时间要求最严格,以及最重要的下半部来使用。目前只有两个子系统在使用:网络子系统以及SCSI子系统

2、执行软中断处理程序的时候,能够相应中断,但自己不能睡眠

3、软中断虽然可以禁止本处理器上的同类软中断,但是对不同处理器的同类软中断是没有限制的(这就意味着潜在的数据并发访问)。因此我们需要考虑同步(加锁、或者使用每处理器数据)

4、对于软中断,只有执行频率很高,连续性要求很高的情况下,才考虑使用

3.4Tasklet3.4.1tasklet的实现

1、tasklet的实现是基于软中断的,所以其本身也是软中断

2、tasklet可以静态定义,也可以动态创建

3.4.2tasklet的调度

1、已触发的tasklet,分为两类:普通优先级 / 高优先级的软中断(前面提到过)

2、Tasklet调度函数首先检查状态,若已经被调度过一次,则立刻返回

3、保存中断状态,禁止本地中断

4、设置tasklet需要处理的标记并开中断

5、在合适的时候,执行do_softirq(),通过内部调用,获取对应状态的tasklet链表,对每一个tasklet进行处理:如果是RUN状态,则跳过[微软用户2],否则标记为RUN并执行之,执行后取消RUN的状态标记

总的来说:每一个tasklet都重复设置HI_SOFTIRQ / TASKLET_SOFTIRQ这2个软中断进行,当一个tasklet被调度时,某一类软中断被唤醒并被特殊的处理函数进行处理,该函数处理执行所有被触发的tasklet(不同类型的tasklet可以同时执行,同类的则只能有一个)

3.4.3tasklet的使用

1、tasklet的使用比较简单,步骤就是创建(静/动)、注册自己的处理函数、调度、或者从待运行链表中删除

2、注意你的处理函数中,不应该使用信号量,不应该进行睡眠

3、注意对和其他类型tasklet、软中断等进行了共享的数据进行加锁保护

3.4.4关于ksoftirqd内核线程

问题描述:软中断与tasklet(实际上也是软中断),被触发的频率实际上是相当高的,而且软中断自己还会重新触发软中断(比如网络子系统),这么一来就很有可能导致用户空间甚至得不到处理器时间,长时间处于饥饿状态

两个简单的方案:一是只要还有被触发的待处理的软中断,本次执行就要负责处理,中途触发的软中断也要处理。二是不处理重新触发的软中断,等到下一次执行。

上述两个方案的不足:前者在系统负载很高的时候,软中断会被不停的触发,以至于用户空间几乎都得不到响应;后者虽然保证了用户进程不会挨饿,但是却牺牲了软中断的性能,在系统空闲时没有充分利用系统资源

ksoftirqd/n 线程:这组内核线程是上述两种方案的最后折中产物,该线程每处理器一个,并以最低nice值运行。内核在每次执行软中断时,都不会处理重新触发的软中断,该工作由ksoftirqd线程进行。当负载较高时,其他线程由于优先级较高,自然能够获取处理器资源;相反当系统较空闲时,ksoftirqd线程能被立刻调度,那么软中断也就可以被立刻执行。下面是ksoftirqd:

650) this.width=650;" src="" title="22.png" />

3.5工作队列3.5.1原理与实现

1、工作队列是内核提供的第三种下半部机制,与前两种有所不同,最主要的区别就是工作队列将工作推后给一个内核线程events/n去执行。à意味着可以进行睡眠,意味着当需要信号量、阻塞、大量内存时,工作队列应该是你的首选

2、每个处理器都会有一个工作者线程,也同时对应一个workque_struct,用户将需要进行处理的工作挂到工作队列中,当线程被调用,则会判断工作队列是否为空,为空则睡眠,否则执行队列中的任务

650) this.width=650;" src="" title="33.png" />

3、工作者队列一般只有系统默认的event类型,对应到系统中是每个处理器一个events线程。如果要增加工作队列类型,那么会导致增加处理器个数个内核线程,所以增加类型一定要慎重

3.5.2使用工作队列

1、可以通过静态方式(DECLARE_WORK)或者动态方式(INIT_WORK)定义自己的工作队列

2、处理函数虽然运行在进程上下文,但不应该访问用户空间的数据

3、默认情况下,处理函数允许响应中断,不持有任何锁,可以在需要的时候进行睡眠

4、调度工作队列时,可以简单指明需要调度,也可以具体指明经过多少时钟节拍后再进行调度。同时,内核也提供了刷新工作队列的接口(睡眠等待所有工作完成),有时候会需要这样的刷新操作来确保不再有待处理的工作

5、如果有需要,可以定义新的工作队列类型

3.6下半部之间加锁

1、不同类tasklet之间需要考虑数据的同步

2、软中断之间需要考虑数据同步

3、进程上下文与下半部共享数据,在访问数据前,要禁止下半部处理,并获取锁

4、中断上下文与下半部共享数据,在访问数据前,要禁止中断,并获取锁

5、工作队列由于可能睡眠,也需要考虑数据同步

4实现自己的中断

我们可以在系统中添加属于自己的中断处理,包括中断处理程序以及可能需要的下半部。

4.1注册中断处理程序

在编写中断处理程序之前,需要申请中断线,并且注册自己的处理函数。这里需要注意的有:中断线是可以多个设备共享的,当共享中断线时,通过设备id来进行区分;初始化硬件和注册中断处理函数的顺序要对,避免在硬件初始化之前就开始执行中断处理程序。

4.2编写中断处理程序

1、通常标记为static

2、至少也要通知硬件,中断已经收到

3、对于较复杂的设备,可能需要发送、接受数据,甚至做一些扩展工作

4、扩展工作应当尽量放到下半部

5、一般会在处理程序中先关中断,所以可以不用考虑重入(中断的嵌套,只会是不同类型的中断)

6、如果访问其他共享数据,比如和其他高优先级中断处理程序共享的数据时,要考虑同步问题

7、多个设备可以共享中断线,当产生中断时,内核会依次调用注册在该中断线上的所有处理函数。这时会根据设备id进行区分

4.3下半部的选择

650) this.width=650;" src="" title="44.png" />

简单来说,这么进行选择:

是否需要睡眠?如果需要,那么就只有选择工作队列

否则就使用tasklet

如果需要很好的性能支持,那就考虑软中断

5关于异常

异常通常由处理器产生,产生时必须考虑与处理器的时钟进行同步。实际上异常也常常被称为同步中断。

处理器在执行到错误指令(比如被0除),或者发生特殊情况(比如缺页)时,必须要靠内核来进行处理,处理器就会产生一个异常

前面几节对硬件产生的异步中断的讨论,大多也适用于异常

[微软用户1]一个设备可以有多个中断处理程序

[微软用户2]同一时间内,同样的tasklet只有一个能够执行


  • 本稿件为独家原创稿件,版权所有,引用或转载请注明出处。
  • 文章出处:
阅读(198) | 评论(0) | 转发(0) |
0

上一篇:应用场景之Dynamic End Point(DEP)IPSec

下一篇:没有了

给主人留下些什么吧!~~