Linux内核:关于中断你须要知道的

1、中断处理程序与其它内核函数真正的差别在于,中断处理程序是被内核调用来对应中断的,而它们执行于中断上下文(原子上下文)中,在该上下文中执行的代码不可堵塞

中断就是由硬件打断操作系统。

2、异常与中断不同。它在产生时必须考虑与处理器时钟同步。异常被称为同步中断,比如:除0、缺页异常、陷入内核(trap)引起系统调用处理程序异常。

3、不同的设备相应的中断不同,而每一个中断都通过一个唯一的数字(中断号)标识。

4、既想让中断处理程序执行得快,又想中断处理程序完毕的工作量多。为了在这两个相悖的目标之间达到一种平衡,一般把中断处理分为两个部分。

中断处理程序是上半部(top half):接收到一个中断。它就立马開始执行,但仅仅做有严格时限的工作。比如对接受的中断进行应答或者复位硬件,这些工作都是在中断被禁止的情况下完毕的(上半部情况下,中断被禁止);还有一部分是下半部(bottom half):可以被同意稍后完毕的工作会推迟到下半部。

(1)为什么要用下半部?

中断处理程序运行的时候,当前中断号相应的中断在全部处理器上都会被屏蔽;更糟糕的是,假设处理器程序是IRQF_DISABLED类型,它运行的时候会把本地的全部中断都屏蔽。然而。缩短中断被屏蔽的时间对系统的响应能力和性能都至关重要,所以。我们应该尽力缩短中断处理程序的时间,办法就是把一些工作放到以后去做。

关键是:在下半部运行的时候,同意响应全部中断

(2)划分中断上半部和下半部的借鉴原则:

  • 假设一个任务对时间很敏感,将其放在中断处理程序中运行
  • 假设一个任务和硬件相关,将其放在中断处理程序中运行。

  • 假设一个任务要保证部被其它中断(特别是同样的中断)打断,将其放置在中断处理程序中运行。
  • 其它全部任务,考虑放置在下半部运行

5、样例。网卡驱动程序,当网卡接收到来自网路的数据包时,须要通知内核数据包到了。中断处理程序(top half)马上開始运行:通知硬件,拷贝最新的网络数据到内存,然后读取网卡很多其它的数据包。这些工作很紧迫。由于网卡上接受数据包的缓存大小固定。下半部:运行处理和操作数据包的其它工作。

6、Linux提供的实现下半部的机制:(上半部的实现机制仅仅有一种:中断处理程序)

在Linux内核2.6中。内核提供了三种不同形式的下半部实现机制:软中断、tasklet和工作队列。这三种机制从2.3開始引入。软中断用的比較少。tasklet是下半部更经常使用的一种形式,可是,tasklet是基于软中断实现的。

(1)假设你想增加一个新的软中断,首先应该问问自己为什么用tasklet实现不了,眼下仅仅有两个子系统(网络和SCSI)直接使用软中断。软中断仅仅有在那些执行频率非常高和连续性非常高的情况下才须要使用。假设不须要扩展到多个处理器。那么就使用tasklet吧。

tasklet本质上也是软中断,仅仅只是同一个处理器程序的多个实例不能再多个处理器上同一时候执行。

下半部何时调用?内核在运行完中断处理器程序以后,do_softirq()函数,于是软中断開始运行中断处理程序留给它去完毕的剩余任务。大部分tasklet和软中断都是在中断处理程序中被设置成待处理状态。所以近期一个中断返回的时候就是运行do_softirq()的最佳时机。

(2)tasklet_action()和tasklet_hi_action()是tasklet处理的核心。

(3)ksoftirqd辅助线程:每一个处理器都有一组辅助处理软中断(和tasklet)的内核线程,当内核中出现大量软中断的时候。这些内核进程就会辅助处理它们。当一个软中断正在运行时,可能会再次触发它自己,内核眼下採取的方案是:不会马上处理又一次触发的软中断。在大量软中断出现的时候。内核会唤醒一组内核线程(nice值是19)来处理这些负载。

for(;;)
{
     if(!softirq_pending(cpu)) //假设没有软中断,则调用schedule()
         schedule();
     
     set_current_state(TASK_RUNNING);
     
     while(softirq_pending(cpu)){
           do_softirq();
           if(need_resched()) //如有必要又一次调度,每次迭代之后都会schedule()以便让更重要的进程得到处理机会
                 schedule();
     }

      set_current_state(TASK_INTERRUPTIBLE);
}

(4)工作队列(work queue)是还有一种将工作任务推后的方式,与软中断和tasklet都不同样。工作队列把工作交给一个内核线程去运行——这个下半部总是在进程上下文中运行。最重要的,工作队列同意又一次调度甚至睡眠

所以,假设推后运行的任务须要睡眠。那么就选择工作队列;假设推后的任务不须要睡眠,那么就选择软件中断或者tasklet。假设须要使用一个能够又一次调度的实体来运行当前中断的下半部任务,就应该使用工作队列。

工作队列是唯一能在进程上下文中运行的下半部实现机制,也仅仅有它才干够睡眠(关键是看你的任务是否须要睡眠)

虽然工作队列的操作处理函数运行在进程上下文中,可是它不能訪问用户空间,由于该内核线程在用户空间没有相关的内存映射。

注意:mmc驱动中用到了工作队列~

Notice:通常在发生系统调用时,内核会代表用户空间的进程执行,此时它才干訪问用户空间。也仅仅有在此时它才会映射用户空间的内存。

(5)下半部机制的选择

对下半部机制的比較
下半部 上下文 顺序运行保障
软中断 中断 没有
tasklet 中断 同类型不能同一时候运行
工作队列 进程 没有(和进程上下文一样被调度)

从易用性角度来看:工作队列 > tasklet > 软中断

总结:驱动程序的编写者。须要做两个选择。首先,你是不是须要一个可调度的实体来运行须要推后完毕的工作——从根本上来说,你须要推后的工作任务有休眠的须要吗?要是有,那么工作队列就是唯一的选择。否则最好用tasklet。

其次,假设必须专注于性能的提高。那么就考虑软中断吧~

(6)使用下半部机制时。即使是在一个单处理器的系统上,避免共享数据的訪问也是至关重要的。禁止下半部的函数有local_bh_disable()和local_bh_enable(),这两个函数仅仅能禁止和激活本地处理器的软中断和tasklet。

由于工作队列是在进程上下文中运行的,不会涉及异步运行的问题。所以也就不是必需禁止它们运行。


7、在驱动程序中,要申请中断(注冊中断处理程序)

request_threaded_irq(unsigned int irq, irq_handler_t handler,
		     irq_handler_t thread_fn,
		     unsigned long flags, const char *name, void *dev);

  irq  须要申请的irq编号,对于ARM体系,irq编号通常在平台级的代码中事先定义好,有时候也能够动态申请。

  handler  中断服务回调函数,该回调运行在中断上下文中,而且cpu的本地中断处于关闭状态。所以该回调函数应该仅仅是运行须要高速响应的操作。运行时间应该尽可能短小,耗时的工作最好留给以下的thread_fn回调处理。

  thread_fn  假设该參数不为NULL。内核会为该irq创建一个内核线程,其中断发生时。假设handler回调返回值是IRQ_WAKE_THREAD,内核将会激活中断线程,在中断线程中,该回调函数将被调用,所以,该回调函数执行在进程上下文中,同意进行堵塞操作

  flags  控制中断行为的位标志,IRQF_XXXX,比如:IRQF_TRIGGER_RISING。IRQF_TRIGGER_LOW。IRQF_SHARED等。在include/linux/interrupt.h中定义。

  name  申请本中断服务的设备名称,同一时候也作为中断线程的名称,该名称能够在/proc/interrupts文件里显示。

  dev  当多个设备的中断线共享同一个irq时,它会作为handler的參数。用于区分不同的设备。free_irq()函数调用的时候,dev的作用就体现出来了。

8、irq_handler_t的类型定义例如以下:

typedef irqreturn_t  (*irq_handler_t)(int,void*);

用typedef 定义了一个函数指针类型irq_handler_t。指向的函数原型返回类型为 irqreturn_t  ;它接收的參数类型就是int 和void* 两个參数

9、request_threaded_irq()函数是能够睡眠的,由于request_threaded_irq()-->proc_mkdir()-->proc_create()-->kmalloc()。而kmalloc()是能够睡眠的。所以,不能在中断上下文中调用该函数。

10、先初始化硬件,然后再注冊中断处理程序,以防止中断处理程序在设备初始化完毕之前就開始运行。

11、free_irq():假设在给定的中断线上没有中断处理程序,则注销响应的处理程序。并禁用当中断线。

12、中断处理程序即使什么工作也不做。至少须要知道产生中断的设备,告诉它已经收到中断了。对于复杂的设备,可能还须要在中断处理器程序中发送和接收数据,以及运行一些扩充的工作。

这些扩充的工作尽可能被推迟到下半部(bottom half)去完毕。

13、中断线和中断号是两个同样的概念,irq

14、Linux中的中断处理程序是无需重入的,当一个给定的中断处理程序正在运行时,对应的中断号在全部处理器上都是被屏蔽掉;所以。同一个中断处理程序绝对不会被同一时候调用以处理嵌套中断。

15、进程上下文:一种内核所处的操作模式,此时内核代表进程运行——比如,运行系统调用或者运行内核线程。

在进程上下文中,能够通过current宏关联当前进程。又由于进程是以进程上下文的形式连接到内核的。因此,进程上下文能够睡眠,也能够调用调度程序

16、中断上下文:与进程没什么关系,与current宏也没有关系,所以中断上下文不能够睡眠。在中断上下文中不能够调用不论什么可能睡眠的函数。

17、中断处理程序打断了其它的代码的运行。所以中断上下文中的代码应该简洁、迅速,尽可能把工作从中断处理程序中分离出来。放在下半部运行。

18、中断处理程序栈的设置是一个内核配置项,假设有的话。是1页大小,即4KB

19、控制中断系统的原因归根结底是须要提供同步。禁止中断提供保护机制,防止来自其它中断程序的并发訪问,也可以禁止内核抢占。提供保护机制,防止来自其它处理器的并发訪问(SMP系统须要考虑)。


參考资料:


原文地址:https://www.cnblogs.com/lcchuguo/p/5333691.html