linux中断相关函数与中断上下文理解

来源:互联网 发布:linux socket编程教程 编辑:程序博客网 时间:2024/04/30 09:31

linux2.5之前的版本中,内核提供了一种”能禁止系统中所有处理器上中断”的方法。而在之后就取消了这些接口。所以:

Local_irq_disable():禁止本地中断 
local_irq_enable():恢复本地中断,这一对函数只要调用一次就会达到所要功能,而不是嵌套的,这样会带来潜在的危险,所以我要需要一种机制可以恢复的以前的状态,而不是单纯的开关,所以内核提供了另外一对函数:

Local_irq_save(flags):禁止本地中断
local_irq_restore(flags):中断被恢复到他们原来的状态
这一对函数是必须在统一函数中调用进行的。
以上所说所有函数都可以在中断中调用,也可以在进程上下文中调用。详见P104

 

内核还提供了禁止指定中断线的接口,禁止某中断线的时候,是对所有处理器都禁止的,上面的函数则是对本地的禁止。

Void disable_irq(unsigned int irq):禁止指定的中断线,函数只有在当前正在执行的所有中断处理程序完成后,才返回

Void synchronize_irq(unsigned int irq):等待特定的中断退出。disable_irq会调用这个函数,来等待中断处理函数的退出,但是如果在中断中调用disable_irq那么将陷入循环

 

Void disanle_irq_nosync(unsigned int irq):禁止指定中断线,不会等待当前中断处理程序执行完毕

Void enable_irq(unsigned int irq):使能指定中断线。    (文档有对这两个函数的些许分析)
禁止指定中断线的函数,是嵌套的,例如,调了两次disable_irq,那么就必须调两次enable_irq才真正的激活了中断线。

以上函数也都是可以在中断上下文中调用的。

另外:个人觉得如果禁止了某中断线,即使开了本地中断,那么那条中断线还是禁止的,这个分析下源码才知道。


Int request_irq(unsigned int irq,irq_handler_t handler,unsigned long flags,const char *name , void *dev):注册中断处理程序的函数。最后一个参数,主要是为了共享中断用的,释放中断处理程序时候,也要用到,这样才能确定删除的是哪一个处理程序,到时候dex只需要传递一个唯一的数值即可,那么,任意有实体的结构体指针就可以满足这个要求了。
Void free_irq(unsigned int irq,void *dev):注销相应的中断处理程序,并释放中断线,如果是共享的话,只有在删除了最后一个处理程序,它才会释放对应的中断线

如下两个宏:

In_interrupt():如果内核处在中断或者下半部处理程序中,返回非零。
In_irq():内核处在中断处理程序中,返回非零。

 

Linux中断处理机制:
一旦遇到中断,处理器就立即停止它正在做的事,然后关闭本地中断系统,跳到内存预定义的位置处执行那里代码。这个预定义的位置是由内核设置的,是中断处理程序的入口点。进入Do_IRQ(),这函数是希望在整个函数中都是中断禁止的。Do_IRQ()禁止任意cpu对这条中断线的中断传递,然后调用了handle_IRQ_event().其内部,若设置IRQF_DISABLED,那么不开中断,若没设置,则开本地中断,但是本身那条中断线是开不了的。然后依次调用中断线上的处理程序。在函数最后禁止本地中断(Do_IRQ()希望它自己整个函数都是禁止中断的)。然后就去执行函数ret_from_intr(),可想到,他最后会开中断返回现场什么的

需明确的是,当一个给定的中断处理程序在执行时,相应的中断线在所有处理器上都会屏蔽,防止在同一中断线上接受另一个新的中断。通常,其他中断线都是打开的,除非申请时候,设置了IRQF_DISABLED.

 

大家都知道在中断上下文中不能睡眠,而在进程上下文中可以睡眠:

一.在进程执行过程,current宏是会指向当前进程的task_struct,一旦调用schedule(),就切换进程上下文,调用另外一个进程运行

二.如果是系统调用,比如是arm处理器,执行swi指令,就切换模式(更改了模式位,可能还对应保护模式的其他东西),这样子就进入内核态,但它此时仍然是代表进程运行在内核态的,swi软件中断优先级很低(中断一般称为异步异常,软件中断和缺页异常等称为同步异常,在arm中swi是最低优先级的),所以在系统调用时候,随时可以接受中断,也可以睡眠,此时的current还是指向当前进程的,一旦睡眠就切换进程上下文。记住一点,swi是程序中你自己加上去的,是你自己控制的,不像其他中断,那么随机。
虽然系统调用实与其他中断实现有点类似,通过IDT表查找入口处理函数,但是系统调用与其他中断最大的不同是,系统调用是代表当前进程执行的,所以current/task_struct是有意义的,这个休眠可以被唤醒

三.如果是中断发生(区别于软件中断;还有软件中断和软中断也是两码子事),就不可以睡眠。
首先明确关中断的时候必须不可以睡眠,不然怎么执行时钟中断呢?(smp系统就不一定会挂掉),时钟中断都没有保障了,这个系统就命不久矣,但是在handle里面实际上是可以睡眠的,因为此时的current是指向了被中断进程的进程描述符,假设中断sleep了, 在调度的时候, 内核将中断的CS:eip和SS:esp保存在被抢占进程的thread_info中, 当进程被重新唤醒的时候,(前提是还有其他进程来唤醒这进程,得要有人爱才行啊) 进程从中断的CS:eip开始执行, 这也能正常执行下去, 中断执行完后, 从ret_from_intr中返回. 可以恢复进程的抢占前的场景. 
但是中断太随机了,你这么一搞:

1.本来中断就应该很快执行完,你还让它睡眠,这本身就不合理
2.这么一个任务好好的运行着,却要承受着无缘无故被你一个随机的中断给打断,而且还不知道什么时候才能再执行,也不合理,中断sleep会增加普通任务的不确定性, 普通任务执行的时间, 实时性都得不到保障. 和中断共享中断号的中断会受到影响, 现在的内核设置了INPROGRESS标志.
3.中断的处理也要依赖这进程的上下文属,也不合理,中断因为借用了被抢占任务的上下文, 所以中断的处理受到任务上下文属性的限制. 等等很多其他问题
总结就是:异步异常(中断)handler不是没有上下文, 而是没有固定的上下文,  如果使用被抢占的任务作为上下文, 一,自身的处理无法得到实时保障,导致系统不确定性, 二,任务受到影响.   (word文档有更详细的叙述)


另外一个可以想的点就是:异常的时候,只是保存了寄存器什么的,后面就代表了进程运行,就算schdle也不怕。但是如果中断的话,会不会进入中断的时候就已经保存了进程上下文,你中断里面再schdle,可能就会覆盖什么的,虽然我觉得很容易做到不覆盖。主要原因还是看上面,我这么提,只是关乎具体实现而已

用户空间切换到内核空间的方法,不是中断就是系统调用,因为两者都无不例外的切换了模式,在那个模式下就可以访问你想要访问的地址了,这个要和保护模式挂钩了

我觉得,中断和异常不同,中断是异步的,异常和系统调用是同步的。 
异常比如缺页异常发生时,当前任务在异常处理完成之前不能继续运行,该异常处理过程和当前任务天然相联系,运行在当前进程的上下文中。 
中断的发生很可能是与当前任务无关的,如果把中断处理实现为强行与当前任务相关联,就会造成当前任务由于和他毫无关系的过程的执行而被剥夺了运行的权利。这样的调度实现是不符合公平合理的原则的,是错误的。

次要:

2.schedule()在切换进程时,保存当前的进程上下文(CPU寄存器的值、进程的状态以及堆栈中的内容),以便以后恢复此进程运行。中断发生后,内核会先保存当前被中断的进程上下文(在调用中断处理程序后恢复)

但在中断处理程序里,CPU寄存器的值肯定已经变化了吧(最重要的程序计数器PC、堆栈SP),如果此时因为睡眠或阻塞操作调用了schedule(),则保存的进程上下文就不是当前的进程context.所以不可以在中断处理程序中调用schedule()

3.2.4内核中schedule()函数本身在进来的时候判断是否处于中断上下文:

if(unlikely(in_interrupt()))BUG();因此,强行调用schedule()的结果就是内核BUG,但我看2.6.18的内核schedule()的实现却没有这句,改掉了.

4.中断handler会使用被中断的进程内核堆栈,但不会对它有任何影响,因为handler使用完后会完全清除它使用的那部分堆栈,恢复被中断前的原貌.

5.处于中断context时候,内核是不可抢占的,因此,如果休眠,则内核一定挂起.

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

先把中断处理流程给出来

1.进入中断处理程序---2.保存关键上下文----3.开中断(sti指令)---4.进入中断处理程序的 handler---5.关中断(cli指令)----6.EOI寄存器(表示中断处理完成)----7.开中断。

复制代码硬中断:对应于上图的123步骤,在这几个步骤中,所有中断是被屏蔽的,如果在这个时候睡眠了,操作系统不会收到任何中断(包括时钟中断),系统就基本处于瘫痪状态(例如调度器依赖的时钟节拍没有等等)软中断:对应上图的4(当然,准确的说应该是4步骤的后面一点,先把话说保险点,免得思一克又开始较真

)。这个时候不能睡眠的关键是因为上下文。

大家知道操作系统以进程调度为单位,进程的运行在进程的上下文中,以进程描述符作为管理的数据结构。进程可以睡眠的原因是操作系统可以切换不同进程的上下文,进行调度操作,这些操作都以进程描述符为支持。

中断运行在中断上下文,没有一个所谓的中断描述符来描述它,它不是操作系统调度的单位。一旦在中断上下文中睡眠,首先无法切换上下文(因为没有中断描述符,当前上下文的状态得不到保存),其次,没有人来唤醒它,因为它不是操作系统的调度单位。

此外,中断的发生是非常非常频繁的,在一个中断睡眠期间,其它中断发生并睡眠了,那很容易就造成中断栈溢出导致系统崩溃。

如 果上述条件满足了(也就是有中断描述符,并成为调度器的调度单位,栈也不溢出了,理论上是可以做到中断睡眠的),中断是可以睡眠的,但会引起很多问题.例 如,你在时钟中断中睡眠了,那操作系统的时钟就乱了,调度器也了失去依据;例如,你在一个IPI(处理器间中断)中,其它CPU都在死循环等你答复,你确 睡眠了,那其它处理器也不工作了;例如,你在一个DMA中断中睡眠了,上面的进程还在同步的等待I/O的完成,性能就大大降低了还可以举出很多例子。 所以,中断是一种紧急事务,需要操作系统立即处理,不是不能做到睡眠,是它没有理由睡眠。

中断产生是随机的,假设某次中断一个内核线程,而且按照你说的方案,这个线程的task结构就会被借用,线程就会去睡眠。被打断线程的优先级如果太低,那 么它很难有机会再执行,某些情况下可能造成系统hang。如果临时提高被打断线程的优先级,那么又需要设计新的唤醒机制来保证阻塞同一锁上的高优先级的线 程被先唤醒。同时,要实现临时提高被打断线程的优先级,又需要再锁的获取流程增加改变优先级的算法。

 

 

活到老,学到老:

上下文context: 上下文简单说来就是一个环境,相对于进程而言,就是进程执行时的环境。具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。
    一个进程的上下文可以分为三个部分:用户级上下文、寄存器上下文以及系统级上下文。
    用户级上下文: 正文、数据、用户堆栈以及共享存储区;
    寄存器上下文: 通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP);
    系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈。

    当发生进程调度时,进行进程切换就是上下文切换(context switch).操作系统必须对上面提到的全部信息进行切换,新调度的进程才能运行。而系统调用进行的模式切换(mode switch)。模式切换与进程切换比较起来,容易很多,而且节省时间,因为模式切换最主要的任务只是切换进程寄存器上下文的切换。

 

 进程上下文和中断上下文是操作系统中很重要的两个概念,这两个概念在操作系统课程中不断被提及,是最经常接触、看上去很懂但又说不清楚到底怎么回事。造成这种局面的原因,可能是原来接触到的操作系统课程的教学总停留在一种浅层次的理论层面上,没有深入去研究。

 

处理器总处于以下状态中的一种:
1、内核态,运行于进程上下文,内核代表进程运行于内核空间;
2、内核态,运行于中断上下文,内核代表硬件运行于内核空间;
3、用户态,运行于用户空间。

 

  用户空间的应用程序,通过系统调用,进入内核空间。这个时候用户空间的进程要传递很多变量、参数的值给内核,内核态运行的时候也要保存用户进程的一些寄存器值、变量等。所谓的“进程上下文”,可以看作是用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等。

  硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。所谓的“中断上下文”,其实也可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被打断执行的进程环境)。

 

LINUX完全注释中的一段话:

  当一个进程在执行时,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文。当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的上下文,以便在再次执行该进程时,能够必得到切换时的状态执行下去。在LINUX中,当前进程上下文均保存在进程的任务数据结构中。在发生中断时,内核就在被中断进程的上下文中,在内核态下执行中断服务例程。但同时会保留所有需要用到的资源,以便中继服务结束时能恢复被中断进程的执行。

 

1,中断要根据前后文来说,一般说中断上下文中不能睡眠,这个中断是指硬件事件发生,触发CPU停止当前活动转而去处理硬件请求.
2,根据硬件请求响应处理逻辑的实时紧要与否,将整个中断处理过程分为上半部和下半部.
3,上半部也就是所谓的硬中断处理逻辑,其要求cpu在收到硬件请求后必须马上处理的事情,比如网卡收到数据包了,把数据包从网卡缓存拷贝到主存(可以由DMA完成,但寄存器的修改以及资源设定还是要由cpu去做)的逻辑就需要cpu立即去做,不做的话,网络新来的数据包就可能丢失.所以这些紧要操作逻辑为硬中断处理.
4,下半部有很多种机制,其中就包括软中断,还有tasklet,workqueue,软中断只是其中的一种,由于历史的原因,有时候是混淆称呼下半部和软中断的.
5,软中断处理的逻辑并不那么严格要求及时,比如对网络数据包的处理,因为逻辑复杂,不适合放到硬中断里去做,因为要尽量保证硬中断短小精悍.
6,linux调度是以进程(或者说线程)来的,即做调度是不同进程上下文的切换.
7,而可以看到软中断逻辑不属于任何进程,所以才不能睡眠,因为一旦睡眠,cpu切换出去,就切不回来了(6一起理解).并不是说无法做到在软/硬中断睡眠,只是目前Linux的实现就是这样的,不允许你这么做,要是这么做了,你就等死.

原创粉丝点击