8 下半部和推后执行的工作

8 下半部和推后执行的工作

上半部——中断处理程序,下半部——软中断、tasklet和工作队列。

上半部的局限性:

  1. 中断处理程序打断了其他代码的执行,并且至少会DISABLE掉当前中断线上的中断(ARM架构处理器会屏蔽当前处理器上所有的中断),所以需要执行的越快越好。
  2. 中断处理程序往往需要对硬件进行操作,所以需要很高的时限要求。
  3. 不能阻塞,限制了所做事情。

注意:上半部是关中断的,下半部会开启中断。

 

 

8.1 下半部

哪些程序适合放在上半部执行,哪些程序适合放在下半部执行:

 

8.1.1 为什么要用下半部

中断处理程序执行时,至少当前中断线在所有处理器上都会被屏蔽(本地中断线全局屏蔽)(ARM会屏蔽当前处理器上所有中断)。所以需要尽力缩短中断处理程序的执行时间,解决方法就是把一些工作放在以后去做。

 

下半部执行时,可以相应所有中断。

8.1.2 下半部的环境

软中断和tasklet

软中断是一组静态定义的下半部接口。有32个,可以在所有处理器上同时执行——即使两个类型相同也可以。使用软中断要十分小心,因为两个相同的软中断有可能同时被执行。

Tasklet是一种基于软中断实现的灵活性强、动态创建的下半部实现机制。两个不同类型的tasklet可以在不同的处理器上同时执行,但类型相同的tasklet不能同时执行。

大多数情况下,使用tasklet就足够了,像网络这样对性能要求非常高的情况才需要使用软中断。

此外,软中断还必须在编译期间就进行静态注册。与此相反,tasklet可以通过代码进行动态注册。

内核定时器

内核定时器把操作推迟到某个确定的时间段之后执行。

 

8.2 软中断

8.2.1 软中断的实现

软中断由softirq_action结构表示,<linux/interrupt.h>

struct softirq_action

{

void (*action)(struct softirq_action *);

};

Kernel/softirq.c中定义了一个包含有32个该结构体的数组。每个被注册的软中断都占据该数组的一项,因此最多可能有32个软中断。

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

 

/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high

   frequency threaded job scheduling. For almost all the purposes

   tasklets are more than enough. F.e. all serial device BHs et

   al. should be converted to tasklets, not to softirqs.

 */

 

enum

{

HI_SOFTIRQ=0,

TIMER_SOFTIRQ,

NET_TX_SOFTIRQ,

NET_RX_SOFTIRQ,

BLOCK_SOFTIRQ,

IRQ_POLL_SOFTIRQ,

TASKLET_SOFTIRQ,

SCHED_SOFTIRQ,

HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the

    numbering. Sigh! */

RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

 

NR_SOFTIRQS

};

  1. 软中断处理程序

 

Void softirq_handler(struct softirq_action *)

 

一个软中断不会抢占另外一个软中断。实际上,唯一可以抢占软中断的是中断处理程序。不过,其他的软中断(甚至是相同类型的软中断)可以在其他处理器上同时执行。

  1. 执行软中断

  一个注册的软中断必须在被标记后才会执行,这被称作触发软中断(raising the softirq)。通常,中断处理程序会在返回前标记它的软中断,使其在稍后执行。

   在下列地方,软中断会被检查和执行:

 

软中断在do_softirq()中执行。如果有待处理的软中断,do_softirq()会循环遍历每一个,调用他们的处理程序。

 

8.2.2 使用软中断

软中断保留给系统中对时间要求最严格以及最重要的下半部使用。

  1. 分配索引

索引号小的软中断在索引号大的软中断之前执行。(软中断不能抢占软中断,只有上半部才可以打断软中断)。

建立新的软中断必须在此枚举类型中加入新的项。而且要根据希望赋予它的优先级来决定加入的位置。

  1. 注册处理程序

open_softirq()注册软中断处理程序。该函数有两个参数:软中断的索引号和处理函数。

软中断处理程序执行的时候,允许响应中断,但他自己不能休眠。在一个处理程序运行的时候,当前处理器上的软中断被禁止(hy硬中断是使能的)。但其他的处理器仍可以执行别的软中断。实际上,如果同一个软中断在他被执行的同时再次被触发了,那么另外一个处理器可以同时运行其处理程序。这意味着任何共享数据(甚至是仅在软中断处理程序内部使用的全局变量)都需要严格的锁保护。这点很重要,它也是tasklet更受青睐的原因(tasklet相同类型的,同一时刻只能在一个处理器上运行)。

   引入软中断的主要原因是其可扩展性(同一个类型的软中断可以在多个处理器上运行)。如果不需要扩展到多个处理器,那么,就使用tasklet吧。Tasklet本质上也是软中断,只不过同一个处理程序的多个实例不能在多个处理器上同时运行

  1. 触发软中断

   Raise_softirq()函数可以将一个软中断设置为挂起状态,让他在下次调用do_softirq()函数时投入运行。例如网络子系统会调用

raise_softirq(NET_TX_SOFTIRQ)

这会触发NET_TX_SOFTIRQ软中断。它的处理程序net_tx_action就会在内核下一次执行软中断时投入运行。

8.3 tasklet

Tasklet是利用软中断实现的一种下半部机制。但是它的接口更简单,锁保护也要求较低(同一种类型的tasklet,只能有一个实例运行)。

软中断只在那些执行频率很高和连续性要求很高的情况下才需要使用。软中断使用者屈指可数。大部分情况下都是使用tasklet。

8.3.1 tasklet的实现

Tasklet由两类软中断代表:HI_SOFTIRQ和TASKLETIRQ。

  1. tasklet结构体

/* Tasklets --- multithreaded analogue of BHs.

 

   Main feature differing them of generic softirqs: tasklet

   is running only on one CPU simultaneously.

 

   Main feature differing them of BHs: different tasklets

   may be run simultaneously on different CPUs.

 

   Properties:

   * If tasklet_schedule() is called, then tasklet is guaranteed

     to be executed on some cpu at least once after this.

   * If the tasklet is already scheduled, but its execution is still not

     started, it will be executed only once.

   * If this tasklet is already running on another CPU (or schedule is called

     from tasklet itself), it is rescheduled for later.

   * Tasklet is strictly serialized wrt itself, but not

     wrt another tasklets. If client needs some intertask synchronization,

     he makes it with spinlocks.

 */

struct tasklet_struct

{

struct tasklet_struct *next;链表中的下一个tasklet

unsigned long state; tasklet状态

atomic_t count;   引用计数器

void (*func)(unsigned long);处理函数

unsigned long data;处理函数参数

};

 

 

2.调度tasklet

8.3.2 使用tasklet

1.声明tasklet

静态声明。

DECLARE_TASKLET(name, func, data)

DECLARE_TASKLET_DISABLE(name, func, data)

 

动态创建。

tasklet_init(t,tasklet_handler,dev)

2.编写tasklet处理程序

3.调度tasklet

tasklet_disable():禁止某个指定的tasklet。

tasklet_enable():激活一个tasklet。

tasklet_kill():从挂起的队列中去掉一个tasklet。(会休眠)

4.ksoftirq

每个处理器都有一组辅助处理软中断和tasklet的内核线程。当内核中出现大量软中断的时候,这些内核进程就会辅助处理他们。

Ksoftirq出现的原因:

对于软中断,内核会选择在几个特殊时机进行处理。而在中断处理程序返回时处理是最常见的。而软中断有时被触发的频率很高,有时还会自行重复触发,如果一直处理软中断,用户空间会得不到执行。

解决:

8.4 工作队列

工作队列可以把工作推后,交由一个内核线程去执行——这个下半部总是在进程上下文中执行(而且总是在内核空间中运行),所以可以睡眠、调度。

如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后的任务不需要睡眠,就选择软中断或者tasklet。

8.4.1 工作队列的实现

工作队列子系统是一个用于创建内核线程的接口,通过它创建的进程负责执行由内核其他部分排到队列里的任务。它创建的这些内核线程称作工作者线程(worker thread)。建议使用内核缺省的工作者线程,也可以建立自己的工作者线程。

  1. 表示线程的数据结构

.

.

.

.

.

8.4.2 使用工作队列

1.创建推后的工作

DECLARE_WORK(name,func,data)

  1. 工作队列处理函数

.

.

.

8.5 下半部机制的选择

软中断提供的执行序列化的保障最少,这就要求软中断处理函数必须格外小心地采取一些步骤确保共享数据的安全,两个甚至更多相同类别的软中断有可能在不同的处理器上同时执行。

两个同种类型的tasklet不能同时执行。Tasklet是有效的软中断,但不能并发运行(不同的tasklet可以在不同的处理器上运行)。

如果推后的任务需要在进程上下文中执行,那么只能选择工作队列。工作队列开销最大(有可能会有进程切换),工作队列只能运行在内核空间。

8.6 在下半部之间加锁

同类型的tasklet共享数据

无需加锁

两个相同类型的tasklet不允许同时执行

不同类型的tasklet共享数据

加自旋锁

不同类型的tasklet可以在不同的处理器上同时执行。这里不需要禁止下半部,因为在同一处理器上绝不会有tasklet相互抢占的情况

软中断共享数据

加自旋锁

不管是相同类型还是不同类型的软中断,都可以在不同的处理器上同时执行。但是同一处理器上的一个软中断绝不会抢占另一个软中断,因此没有必要禁止下半部

进程和下半部共享数据

(进程) 1.禁止下半部处理

2.加锁

 

中断上半部和下半部共享数据

(下半部) 1.禁止中断

2.加自旋锁

 

工作队列中共享数据

加锁

 

 

8.7 禁止下半部

 

 

猜你喜欢

转载自blog.csdn.net/u014426028/article/details/108474868