中断.docx
- 文档编号:18613227
- 上传时间:2023-08-20
- 格式:DOCX
- 页数:79
- 大小:55.66KB
中断.docx
《中断.docx》由会员分享,可在线阅读,更多相关《中断.docx(79页珍藏版)》请在冰点文库上搜索。
中断
中断
Linux系统中有很多不同的硬件设备。
你可以同步使用这些设备,也就是说你可以发出一个请求,然后等待一直到设备完成操作以后再进行其他的工作。
但这种方法的效率却非常的低,因为操作系统要花费很多的等待时间。
一个更为有效的方法是发出请求以后,操作系统继续其他的工作,等设备完成操作以后,给操作系统发送一个中断,操作系统再继续处理和此设备有关的操作。
在将多个设备的中断信号送往CPU的中断插脚之前,系统经常使用中断控制器来综合多个设备的中断。
这样即可以节约CPU的中断插脚,也可以提高系统设计的灵活性。
中断控制器用来控制系统的中断,它包括屏蔽和状态寄存器。
设置屏蔽寄存器的各个位可以允许或屏蔽某一个中断,状态寄存器则用来返回系统中正在使用的中断。
大多数处理器处理中断的过程都相同。
当一个设备发出中段请求时,CPU停止正在执行的指令,转而跳到包括中断处理代码或者包括指向中断处理代码的转移指令所在的内存区域。
这些代码一般在CPU的中断方式下运行。
在此方式下,将不会再有中断发生。
但有些CPU的中断有自己的优先权,这样,更高优先权的中断则可以发生。
这意味着第一级的中断处理程序必须拥有自己的堆栈,以便在处理更高级别的中断前保存CPU的执行状态。
当中断处理完毕以后,CPU将恢复到以前的状态,继续执行中断处理前正在执行的指令。
中断处理程序十分简单有效,这样,操作系统就不会花太长的时间屏蔽其他的中断。
[设置Softirq]
cpu_raise_softirq是一个轮训,唤醒ksoftirqd_CPU0内核线程,进行管理
cpu_raise_softirq
|__cpu_raise_softirq
|wakeup_softirqd
|wake_up_process
·cpu_raise_softirq[kernel/softirq.c]
·__cpu_raise_softirq[include/linux/interrupt.h]
·wakeup_softirq[kernel/softirq.c]
·wake_up_process[kernel/sched.c]
[执行Softirq]
当内核线程ksoftirqd_CPU0被唤醒,它会执行队列里的工作。
当然ksoftirqd_CPU0也是一个死循环:
for(;;){
if(!
softirq_pending(cpu))
schedule();
__set_current_state(TASK_RUNNING);
while(softirq_pending(cpu)){
do_softirq();
if(current->need_resched)
schedule
}
__set_current_state(TASK_INTERRUPTIBLE)
}
·ksoftirqd[kernel/softirq.c]
[目录]
________________________________________
软中断「一」
一、引言
软中断是linux系统原"底半处理"的升级,在原有的基础上发展的新的处理方式,以适应多cpu、多线程的软中断处理。
要了解软中断,我们必须要先了原来底半处理的处理机制。
二、底半处理机制(基于2.0.3版本)
某些特殊时刻我们并不愿意在核心中执行一些操作。
例如中断处理过程中。
当中断发生时处理器将停止当前的工作,操作系统将中断发送到相应的设备驱动上去。
由于此时系统中其他程序都不能运行,所以设备驱动中的中断处理过程不宜过长。
有些任务最好稍后执行。
Linux底层部分处理机制可以让设备驱动和Linux核心其他部分将这些工作进行排序以延迟执行。
系统中最多可以有32个不同的底层处理过程;bh_base是指向这些过程入口的指针数组。
而bh_active和bh_mask用来表示那些处理过程已经安装以及那些处于活动状态。
如果bh_mask的第N位置位则表示bh_base的第N个元素包含底层部分处理例程。
如果bh_active的第N位置位则表示第N个底层处理过程例程可在调度器认为合适的时刻调用。
这些索引被定义成静态的;定时器底层部分处理例程具有最高优先级(索引值为0),控制台底层部分处理例程其次(索引值为1)。
典型的底层部分处理例程包含与之相连的任务链表。
例如immediate底层部分处理例程通过那些需要被立刻执行的任务的立即任务队列(tq_immediate)来执行。
--引自DavidARusling的《linux核心》。
三、对2.4.1软中断处理机制
下面,我们进入软中断处理部份(softirq.c):
由softirq.c的代码阅读中,我们可以知道,在系统的初始化过程中(softirq_init()),它使用了两个数组:
bh_task_vec[32],softirq_vec[32]。
其中,bh_task_vec[32]填入了32个bh_action()的入口地址,但soft_vec[32]中,只有softirq_vec[0],和softirq_vec[3]分别填入了tasklet_action()和tasklet_hi_action()的地址。
其余的保留它用。
当发生软中断时,系统并不急于处理,只是将相应的cpu的中断状态结构中的active的相应的位置位,并将相应的处理函数挂到相应的队列,然后等待调度时机来临(如:
schedule(),
系统调用返回异常时,硬中断处理结束时等),系统调用do_softirq()来测试active位,再调用被激活的进程在这处过程中,软中断的处理与底半处理有了差别,active和mask不再对应bh_base[nr],而是对应softirq_vec[32]。
在softirq.c中,我们只涉及了softirq_vec[0]、softirq_vec[3]。
这两者分别调用了tasklet_action()和tasklet_hi_action()来进行后续处理。
这两个过程比较相似,大致如下:
1锁cpu的tasklet_vec[cpu]链表,取出链表,将原链表清空,解锁,还给系统。
2对链表进行逐个处理。
3有无法处理的,(task_trylock(t)失败,可能有别的进程锁定),插回系统链表。
至此,系统完成了一次软中断的处理。
接下来有两个问题:
1bh_base[]依然存在,但应在何处调用?
2tasklet_vec[cpu]队列是何时挂上的?
四、再探讨
再次考查softirq.c的bh_action()部份,发现有两个判断:
A:
if(!
spin_trylock(&global_bh_lock))goto:
rescue指明如果global_bh_lock不能被锁上(已被其它进程锁上),则转而执行rescue,将bh_base[nr]挂至tasklet_hi_vec[cpu]队列中。
等候中断调度。
B:
if(!
hardirq_trylock(cpu))gototescueunlock此时有硬中断发生,放入队列推迟执行。
若为空闲,现在执行。
由此可见,这部分正是对应底半处理的程序,bh_base[]的延时处理正是底半处理的特点,可以推测,如果没有其它函数往tasklet_hi_vec[cpu]队列挂入,那tasklet_hi_vec[cpu]正完全对应着bh_base[]底半处理
在bh_action()中,把bh_ation()挂入tasklet_hi_vec[cpu]的正是mark_bh(),在整个源码树中查找,发现调用mark_bh()的函数很多,可以理解,软中断产生之时,相关的函数会调用mark_bh(),将bh_action挂上tasklet_hi_vec队列,而bh_action()的作用不过是在发现bh_base[nr]暂时无法处理时重返队列的方法。
由此可推测tasklet_vec队列的挂接应与此相似,查看interrupt.h,找到tasklet_schedule()函数:
157staticinlinevoidtasklet_schedule(structtasklet_struct*t)
158{
159if(!
test_and_set_bit(TASKLET_STATE_SCHED,&t->state)){
160intcpu=smp_processor_id();
161unsignedlongflags;
162
163local_irq_save(flags);
164t->next=tasklet_vec[cpu].list;
165tasklet_vec[cpu].list=t;/*插入队列。
166__cpu_raise_softirq(cpu,TASKLET_SOFTIRQ);
167local_irq_restore(flags);
168}
169}
正是它为tasklet_vec[cpu]队列的建立立下了汗马功劳,在源码树中,它亦被多个模块调用,来完成它的使命。
至此,我们可以描绘一幅完整的软中断处理图了。
现在,再来考查do_softirq()的softirq_vec[32],在interrupt.h中有如下定义:
56enum
57{
58HI_SOFTIRQ=0,
59NET_TX_SOFTIRQ,
60NET_RX_SOFTIRQ,
61TASKLET_SOFTIRQ
62};
这四个变量应都是为softirq_vec[]的下标,那么,do_softirq()也将会处理NET_TX_SOFTIRQ和NET_RX_SOFTIRQ,是否还处理其它中断,这有待探讨。
也许,这个do_softirq()有着极大的拓展性,等着我们去开发呢。
主要通过__cpu_raise_softirq来设置
在hi_tasklet(也就是一般用于bh的)的处理里面,在处理完当前的队列后,会将补充的队列重新挂上,然后标记(不管是否补充队列里面有tasklet):
local_irq_disable();
t->next=tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list=t;
__cpu_raise_softirq(cpu,HI_SOFTIRQ);
local_irq_enable();
因此,对mark_bh根本不用设置这个active位。
对于一般的tasklet也一样:
local_irq_disable();
t->next=tasklet_vec[cpu].list;
tasklet_vec[cpu].list=t;
__cpu_raise_softirq(cpu,TASKLET_SOFTIRQ);
local_irq_enable();
其它的设置,可以检索上面的__cpu_raise_softirq
bottomhalf,softirq,tasklet,tqueue
[bottomhalf]
bh_base[32]
|
\/
bh_action();
|
\/
bh_task_vec[32];
|mark_bh(),tasklet_hi_schedule()
\/
task_hi_action
bh_base对应的是32个函数,这些函数在bh_action()中调用
staticvoidbh_action(unsignedlongnr)
{
intcpu=smp_processor_id();
if(!
spin_trylock(&global_bh_lock))
gotoresched;
if(!
hardirq_trylock(cpu))
gotoresched_unlock;
if(bh_base[nr])
bh_base[nr]();
hardirq_endlock(cpu);
spin_unlock(&global_bh_lock);
return;
resched_unlock:
spin_unlock(&global_bh_lock);
resched:
mark_bh(nr);
}
在软中断初始化时,将bh_action()放到bh_task_vec[32]中,bh_task_vec[32]中元素的类型是tasklet_struct,系统使用mark_bh()或task_hi_schedule()函数将它挂到task_hi_vec[]的对列中,在系统调用do_softirq()时执行。
staticinlinevoidmark_bh(intnr)
{
tasklet_hi_schedule(bh_task_vec+nr);
}
staticinlinevoidtasklet_hi_schedule(structtasklet_struct*t)
{
if(!
test_and_set_bit(TASKLET_STATE_SCHED,&t->state)){
intcpu=smp_processor_id();
unsignedlongflags;
local_irq_save(flags);
t->next=tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list=t;
__cpu_raise_softirq(cpu,HI_SOFTIRQ);
local_irq_restore(flags);
}
}
[softirq]
softirq_vec[32];
structsoftirq_action
{
void(*action)(structsoftirq_action*);
void*data;
};
软中断对应一个softirq_action的结构,在do_softirq()中调用相应的action()做处理。
软中断初始化时只设置了0,3两项,对应的action是task_hi_action和task_action.
1:
task_hi_action
/\
|
tasklet_hi_vec[NR_CPU]
structtasklet_headtasklet_hi_vec[NR_CPUS]__cacheline_aligned;
structtasklet_head
{
structtasklet_struct*list;
}__attribute__((__aligned__(SMP_CACHE_BYTES)));
task_hi_action处理的对象是一个tasklet的队列,每个cpu都有一个对应的tasklet队列,
它在tasklet_hi_schedule中动态添加。
3:
task_action
/\
|
tasklet_vec[NR_CPU]
[tasklet]
structtasklet_struct
{
structtasklet_struct*next;
unsignedlongstate;
atomic_tcount;
void(*func)(unsignedlong);
unsignedlongdata;
};
从上面的分析来看tasklet只是一个调用实体,在do_softirq()中被调用。
softirq的组织和结构才是最重要的。
硬中断
这里总结一下Linux设备驱动程序中涉及的中断机制。
一、前言
Linux的中断宏观分为两种:
软中断和硬中断。
声明一下,这里的软和硬的意思是指和软件相关以及和硬件相关,而不是软件实现的中断或硬件实现的中断。
软中断就是"信号机制"。
软中断不是软件中断。
Linux通过信号来产生对进程的各种中断操作,我们现在知道的信号共有31个,其具体内容这里略过,感兴趣读者可参看相关参考文献[1]。
一般来说,软中断是由内核机制的触发事件引起的(例如进程运行超时),但是不可忽视有大量的软中断也是由于和硬件有关的中断引起的,例如当打印机端口产生一个硬件中断时,会通知和硬件相关的硬中断,硬中断就会产生一个软中断并送到操作系统内核里,这样内核就会根据这个软中断唤醒睡眠在打印机任务队列中的处理进程。
硬中断就是通常意义上的"中断处理程序",它是直接处理由硬件发过来的中断信号的。
当硬中断收到它应当处理的中断信号以后,就回去自己驱动的设备上去看看设备的状态寄存器以了解发生了什么事情,并进行相应的操作。
对于软中断,我们不做讨论,那是进程调度里要考虑的事情。
由于我们讨论的是设备驱动程序的中断问题,所以焦点集中在硬中断里。
我们这里讨论的是硬中断,即和硬件相关的中断。
二、中断产生
要中断,是因为外设需要通知操作系统她那里发生了一些事情,但是中断的功能仅仅是一个设备报警灯,当灯亮的时候中断处理程序只知道有事情发生了,但发生了什么事情还要亲自到设备那里去看才行。
也就是说,当中断处理程序得知设备发生了一个中断的时候,它并不知道设备发生了什么事情,只有当它访问了设备上的一些状态寄存器以后,才能知道具体发生了什么,要怎么去处理。
设备通过中断线向中断控制器发送高电平告诉操作系统它产生了一个中断,而操作系统会从中断控制器的状态位知道是哪条中断线上产生了中断。
PC机上使用的中断控制器是8259,这种控制器每一个可以管理8条中断线,当两个8259级联的时候共可以控制15条中断线。
这里的中断线是实实在在的电路,他们通过硬件接口连接到CPU外的设备控制器上。
三、IRQ
并不是每个设备都可以向中断线上发中断信号的,只有对某一条确定的中断线勇有了控制权,才可以向这条中断线上发送信号。
由于计算机的外部设备越来越多,所以15条中断线已经不够用了,中断线是非常宝贵的资源。
要使用中断线,就得进行中断线的申请,就是IRQ(InterruptRequirement),我们也常把申请一条中断线成为申请一个IRQ或者是申请一个中断号。
IRQ是非常宝贵的,所以我们建议只有当设备需要中断的时候才申请占用一个IRQ,或者是在申请IRQ时采用共享中断的方式,这样可以让更多的设备使用中断。
无论对IRQ的使用方式是独占还是共享,申请IRQ的过程都是一样的,分为3步:
1.将所有的中断线探测一遍,看看哪些中断还没有被占用。
从这些还没有被占用的中断中选一个作为该设备的IRQ。
2.通过中断申请函数申请选定的IRQ,这是要指定申请的方式是独占还是共享。
3.根据中断申请函数的返回值决定怎么做:
如果成功了万事大吉,如果没成功则或者重新申请或者放弃申请并返回错误。
申请IRQ的过程,在参考书的配的源代码里有详细的描述,读者可以通过仔细阅读源代码中的short一例对中断号申请由深刻的理解。
四、中断处理程序
Linux中的中断处理程序很有特色,它的一个中断处理程序分为两个部分:
上半部(tophalf)和下半部(bottomhalf)。
之所以会有上半部和下半部之分,完全是考虑到中断处理的效率。
上半部的功能是"登记中断"。
当一个中断发生时,他就把设备驱动程序中中断例程的下半部挂到该设备的下半部执行队列中去,然后就没事情了--等待新的中断的到来。
这样一来,上半部执行的速度就会很快,他就可以接受更多她负责的设备产生的中断了。
上半部之所以要快,是因为它是完全屏蔽中断的,如果她不执行完,其它的中断就不能被及时的处理,只能等到这个中断处理程序执行完毕以后。
所以,要尽可能多得对设备产生的中断进行服务和处理,中断处理程序就一定要快。
但是,有些中断事件的处理是比较复杂的,所以中断处理程序必须多花一点时间才能够把事情做完。
可怎么样化解在短时间内完成复杂处理的矛盾呢,这时候Linux引入了下半部的概念。
下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的。
下半部几乎做了中断处理程序所有的事情,因为上半部只是将下半部排到了他们所负责的设备的中断处理队列中去,然后就什么都不管了。
下半部一般所负责的工作是察看设备以获得产生中断的事件信息,并根据这些信息(一般通过读设备上的寄存器得来)进行相应的处理。
如果有些时间下半部不知道怎么去做,他就使用著名的鸵鸟算法来解决问题--说白了就是忽略这个事件。
由于下半部是可中断的,所以在它运行期间,如果其它的设备产生了中断,这个下半部可以暂时的中断掉,等到那个设备的上半部运行完了,再回头来运行它。
但是有一点一定要注意,那就是如果一个设备中断处理程序正在运行,无论她是运行上半部还是运行下半部,只要中断处理程序还没有处理完毕,在这期间设备产生的新的中断都将被忽略掉。
因为中断处理程序是不可重入的,同一个中断处理程序是不能并行的。
在LinuxKernel2.0以前,中断分为快中断和慢中断(伪中断我们这里不谈),其中快中断的下半部也是不可中断的,这样可以保证它执行的快一点。
但是由于现在硬件水平不断上升,快中断和慢中断的运行速度已经没有什么差别了,所以为了提高中断例程事务处理的效率,从Linuxkernel2.0以后,中断处理程序全部都是慢中断的形式了--他们的下半部是可以被中断的。
但是,在下半部中,你也可以进行中断屏蔽--如果某一段代码不能被中断的话。
你可以使用cti、sti或者是save_flag、restore_flag来实现你的想法。
至于他们的用法和区别,请参看本文指定参考书中断处理部分。
进一步的细节请读者参看本文指定参考书,这里就不再所说了,详细介绍细节不是我的目的,我的目的是整理概念。
五、置中断标志位
在处理中断的时候,中断控制器会屏蔽掉原先发送中断的那个设备,直到她发送的上一个中断被处理完了为止。
因此如果发送中断的那个设备载中断处理期间又发送了一个中断,那么这个中断就被永远的丢失了。
之所以发生这种事情,是因为中断控制器并不能缓冲中断信息,所以当前一个中断没有处理完以前又有新的中断到达,他肯定会丢掉新的中断的。
但是这种缺陷可以通过设置主处理器(CPU)上的"置中断标志位"(sti)来解决,因为主处理器具有缓冲中断的功能。
如果使用了"置中断标志位",那么在处理完中断以后使用sti函数就可以使先前被屏蔽的中断得到服务。
六、中断处理程序的不可重入性
上一节中我们提到有时候需要屏蔽中断,可是为什么要将这个中断屏蔽掉呢?
这并不是因为技术上实现不了同一中断例程的并行,而是出于管理上的考虑。
之所以在中断处理的过程中要屏蔽同一IRQ来的新中断,是因为中断处理程序是不可重入的,所以不能并行执行同一个中断处理程序。
在这里我们举一个例子,从这里子例中可以看出如果一个中断处理程序是可以并行的话,那么很有可能会发生驱动程序锁死的情况。
当驱动程序锁死的时候,你的操作系统并不一定会崩溃,但是锁死的驱动程序所支持的那个设备是不能再使用了--设备驱动程序死了,设备也就死了。
A是一段代码,B是操作设备寄存器R1的代码,C是操作设备寄存器R2的代码。
其中激发PS1的事件会使A1产生一个中断,然后B1去读R1中已有的数据,然后代码C1向R2中写数据。
而激发PS2的事件会使A2产生一个中断,然后B2删除R1中的数据,然后C2读去R2中的数据。
如果PS1先产生,且当他执行到A1和B1之间的时候,如果PS2产生了,这是A2会产生一个中断,将PS2中断掉(挂到任务队列的尾部),然后删除了R1的内容。
当PS2运行到C2时,由于C1还没有向R2中写数据,所以C2将会在这里被挂起,PS2就睡眠在代码C2上,直到有数据可读的时候被信号唤
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 中断
![提示](https://static.bingdoc.com/images/bang_tan.gif)