您的位置:首页 > 运维架构 > Linux

[笔记分享] [OS] Linux的中断处理

2017-08-25 13:34 393 查看
Platform: msm8x60

Kernel: 2.6

介绍

Linux将中断分为中断上半部和下半部。上半部用来处理紧急的和硬件操作相关的,下半部用来处理能够被允许推迟完成的中断处理部分。两者之间的界限依情况划分。

异常和中断不同,必须考虑时钟的同步,也称同步中断,如除0、缺页等。这里我们只讨论异步中断。

中断处理程序注册

注册函数如下:



irq:要分配的中断号

handler: 中断处理函数,中断触发时会被调用

flags:处理中断标志。有IRQF_DISABLED、IRQF_SHARED、IRQF_SAMPLE_RANDOM等几种,这三种比较常用。IRQF_DISABLED表示进入中断处理程序时禁止其他本地中断,默认是中断开启的。IRQF_SHARED表示中断线是共享的。

name:中断名

dev: 中断共享时用到,用来判断是哪个中断触发。

request_irq()会导致睡眠,因为其在注册过程中调用了kmalloc(),而这个函数是会引起睡眠的。因此不能用于中断上下文中或者其他不允许阻塞的地方。

相对应的在释放中断处理程序的时候用下面函数释放中断:



如果中断时共享的,则仅删除dev_id所对应的处理程序,直到最后一个中断被删除的时候才禁用此中断线。

中断处理函数格式如下:



其返回类型为irqreturn_t,可分两种:IRQ_NONE和IRQ_HANDLED。当检测到中断正常时我们返回IRQ_HANDLED,否则返回IRQ_NONE。

另外,中断处理程序是不用考虑重入的,因为当一个中断处理程序运行时,相同线上的中断都会被屏蔽。当然,通常这时其他线上的中断是被打开的,这样使得更高优先级的中断能被处理。

中断上下文

和进程上下文类似,当内核在执行一个中断处理程序或者下半部时,内核处于中断上下文。在进程上下文中,进程可以通过current宏关联到当前进程,也可以睡眠,也可以调度程序。但是在内核上下文中,其和进程没什么关系,和current宏业不相关,不能睡眠,不然谁又知道什么时候能唤醒如何唤醒呢?

进程有各自的内核栈,一般为两页大小,以前中断和其共用内核栈,必须要非常节省。现在进程的内核栈缩小为一页,而中断又有了自己的中断栈(全部中断共用一页),平均比使用内核栈要大得多。但是要注意的是在内核中要尽量少使用栈,否则可能导致益处。

中断控制

当我们需要控制中断内的数据要同步时,我们可以控制中断的禁止或者禁止内核抢占来实现。关于数据的同步,SMP的数据保护问题我们将在后面内核同步章节中讲到,这里我们只讨论如何禁止中断。

禁止和使能本地中断函数如下:



宏的实现和平台相关。注意,调用这两个函数有潜在的危险,如在local_irq_disable()之前本来就是禁止中断的,之后我们又相应地调用了local_irq_disalbe(),这样一来中断被我们打开了!

因此,我们用保存操作中断以前的中断状态的方法。在禁止中断之前保存中断系统的状态,然后在准备激活中断时,将中断恢复到它们原来的状态就可以了。函数如下:



这两个函数也和体系结构相关。注意,对local_irq_save()和local_irq_restore()的调用必须在同一函数中进行。

除了上述函数外,我们还有禁止指定中断线函数,这里就不作介绍了。

有时我们需要判断当前是否处于中断状态,这时下面这几个宏可以派上用场了:



In_irq():判断是否处于硬中断

In_softirq():判断是否处于下半部

In_interrupt():判断是否处于硬中断或下半部

其实本质都是通过preemmpt_count来判断,看下面实现就明白了。注意preempt_count还有一部分是用来对内核是否可抢占进行计数的。



中断下半部

好了,中断上半部用起来不是很难,咱们现在看下半部。相对来说,下半部处理可往后推迟的事情。如网卡驱动在硬中断部分从网卡接收数据,然后在下半部对网卡数据进行处理。

当前内核,我们有三种机制实现下半部: 软终端、tasklet和工作队列。其中tasklet通过软终端实现,而工作队列和它们完全不同。

a) 软中断

软中断是在编译时静态分配的。它不能像tasklet那样进行动态分配。由softirq_action 结构表示,如下:



action()为软中断处理函数,另外还定义了一个包含32个该结构体的数组:



软中断类型列表如下:



0优先级最先运行,依次排列。一般我们自己注册的软中断处于3和5之间。注册方法如下:



其实也就是将软中断处理函数加入到softirq_vec中去。举例:

Open_softirq(NET_TX_SOFTIRQ, net_tx_action);

每个被注册的软中断占据一项,因此最多32个。

一个注册号的软中断必须要触发才能执行,通常在中断处理程序返回前标志它的软中断(rasie_softirq()),使其稍后被处理。待处理的软中断在以下地方被检查:

1. 从一个硬件中断代码处返回时

2. 在ksoftirqd内核线程中

3. 显示检查中

不管用什么方法,软中断都要在do_softirq()中执行。

一个软中断不会抢占另一个软中断,唯一可抢占它的只有中断处理程序,因为在处理软中断处理程序的时候,中断时开启的,但它和中断处理程序一样不能休眠。本地软中断被禁止,但是其他处理器上可执行软中断,甚至是同类的中断,那么处理函数就会被执行两次,数据就会遭到破坏了。因此如果没必要的话,我们就用tasklet, 它的同一个处理函数不会在两个处理器上同时运行,这样也就避免了加锁的麻烦。

b) tasklet

tasklet基于软中断,虽然很相似,但接口更简单,锁保护要求也低,通常我们选择使用tasklet。只有那些执行频率很高的或者连续性要求很高的才用软中断。

Tasklet由两类软中断代表: HI_SOFTIRQ和 TASKLET_SOFTIRQ。两者区别在于前者优先级高先执行而已。Tasklet结构如下:



Next将注册的tasklet给链接起来。

State有三种:TASKLET_STATE_SCHED表明已被调度,准备投入运行;TASKLET_STATE_RUN表明正在运行。

Count是tasklet引用计数器,0表示被激活,设置为挂起,这样才能运行。不为0表示被禁止,不许执行。

已调度的tasklet存在tasklet_vec或tasklet_hi_vec中,每个元素一个tasklet。由tasklet_schedule()或tasklet_hi_schedule()调度。当被调度时,内核就会唤醒之前注册的HI_SOFTIRQ或TASKLET_SOFTIRQ这两个软中断,然后执行所有已调度的tasklet。该函数保证只有一个同一类别的tasklet会被执行,甚至是在不同处理器上,但是允许不同类别的tasklet执行。

c) work queue

这种方式的下半部我们不怎么用,这里就不作详细介绍了。我们只要知道它是将推迟的任务放在进程上下文中完成的。因此其造成的开销最大,因为要牵扯到内核线程甚至上下文切换。

下面是三种下半部机制的比较:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: