linux内核软中断及tasklet

来源:互联网 发布:美工网 编辑:程序博客网 时间:2024/04/19 06:18

在内核执行的几个任务之间有些是不紧急的:在必要情况下他们可以延迟一段时间。一个中断服务程序的几个中断服务例程之间是串行执行的,并且通常在一个中断处理程序结束前,不应该再出现这个中断。相反,可延迟中断可以在开中断的情况下执行。把可延迟的中断从中断处理程序中抽取出来,有助于使内核保持较短的响应时间。

软中断和tasklet有密切关系,tasklet是在软中断之上实现。事实上,出现在内核代码中的术语软中断常常表示可延迟函数的所有种类。另一种被广泛使用的术语:中断上下文表示内核正在执行一个中断处理程序或一个可延迟函数。

软中断的分配是静态的,而tasklet的分配和初始化可以在运行时进行。软中断可以并发地在多个cpu上运行。因此,软中断可以是可重入函数而且必须明确的使用自旋锁来保护变量,tasklet不必担心这些问题,因为内核对tasklet的执行有力更加严格的控制。相同类型的tasklet总是被串行执行,换句话说,不能在两个cpu上同时运行相同类型的tasklet。但是类型不同的tasklet可以在几个cpu上并发执行。tasklet的串行化使tasklet函数不必是可重入的,因此简化了设备驱动程序开发者的工作。

一般而言,在可延迟函数上可以执行四种操作

初始化:定义一个新的可延迟函数;这个操作通常有内核初始化或加载模块时执行。

激活:标记一个可延迟函数为挂起(在可延迟函数的下一轮调度)。激活可以在任何时候进行(即使正在处理中断)

屏蔽:有选择的评比一个可延迟函数,这样即使激活,也不会执行它

执行:执行一个挂起的可延迟函数和其他所有挂起的可延迟函数;执行时在特定的时间进行的。

激活和执行通常绑定在一起:由给定cpu激活的一个可延迟函数必须在同一个cpu上执行,这个更有利于利用cpu的硬件高速缓存,因为激活和执行可能使用相同的数据结构。

软中断

linux2.6使用有限个软中断。在很多场合,tasklet是足够用的,且更容易编写,因为tasklet不必是可重入的。

Linux2.6使用的软中断:

HI_SOFTIRQ   0       处理高优先级的tasklet

TIMER_SOFTIRQ     1  和时钟中断相关的tasklet

NET_TX_SOFTIRQ    2     把数据包传送到网卡

NET_RX_SOFTIRQ   3   从网卡接受数据包

SCSI_SOFTIRQ  4 SCSI命令的后台中断处理

TASKLET_SOFTIRQ    5    处理常规tasklet

一个软中断的下标决定了优先级:低下标意味着高优先,因为软中断函数从下标0开始执行

软中断使用的数据结构:软中断使用的数据结构是softirq_vec数组,该数组包含类型为softirq_action的32个元素。一个软中断的优先级是相应的softirq_action元素在数组内的下标,softirq_action数据结构包含两个字段:指向软中断函数的一个action指针和指向软中断函数需要的同属数据结构的打他指针。

另外一个关键的字段是32位的preempt_count字段,用它来跟踪内核抢占和内核控制路径嵌套,该字段存放在每个进程描述符的thread_info字段,preempt_count字段的编码表示三个不同的计数器和一个标志。

0-7 抢占计数器   8-15软中断计数器 16-27硬中断计数器 28 preempt_active标志

第一个计数器记录显示禁用本地cpu内核抢占的次数,值等于0表示允许内核抢占。

第二个计数器表示可延迟函数被禁用的程度,0表示可延迟函数处于激活状态

第三个计数器表示在本地cpu上中断处理程序的嵌套数

给preempt_count字段起这个名字的理由是很充分的:当内核代码明确不允许发生抢占或内核在中断上下文运行时,必须禁用内核抢占的功能,因此为了确定是否能够抢占当前进程,内核快速检查preempt_count字段中相应值是否为0.

处理软中断:open_softirq()函数处理软中断的初始化。它使用三个函数:软中断下标、指向要执行的软中断函数及指向可能由软中断函数使用的数据结构的指针。

raise_softirq()函数用来激活软中断,他接受软中断下标nr作为参数。

tasklet:tasklet是IO驱动程序中实现可延迟函数的首选方法。如前所述,tasklet建立在两个叫做

HI_SOFTIRQ   0       处理高优先级的tasklet

TASKLET_SOFTIRQ    5    处理常规tasklet

的软中断之上。几个tasklet可以与同一个软中断关联,每个tasklet执行自己的函数。两个软中断之间没有真正的差别,只不过do_softirq()先执行hi_softirq的tasklet,后执行TASKLET_SOFTIRQ的tasklet。

tasklet和高优先级的tasklet分别存放在taskelt_vec和tasklet_hi_vec数组中。二者都包含类型为tasklet_head的NR_CPUS个元素,每个元素都由一个指向tasklet描述符链表的指针组成。tasklet描述符是一个tasklet_struct类型的数据结构,其字段如下:

next指向链表中下一个描述符的指针

state tasklet的状态

count  锁计数器

func 指向tasklet函数的指针

data  一个无符号整数,可以由tasklet函数来使用

state字段的两个标志:

TASKLET_STATE_SCHED:该标志被设置时,表示tasklet是挂起的,也意味着tasklet描述符被插入到tasklet_vec和tasklet_hi_vec数组的其中一个链表中

TASKLET_STATE_RUN:该标志被设置时,表示tasklet正在被执行;在单处理器系统上不使用这个标志,因为没有必要检查特定的tasklet是否在允许。

让我们假定,你正在写一个设备驱动程序,且使用tasklet,应该做些什么呢?首先,你应该分配一个新的tasklet_struct数据结构,并调用tasklet_int()初始化;该函数接收的参数为tasklet描述符的地址、tasklet函数的地址和他的可选整形参数。

调用tasklet_disable_nosync()或tasklet_disable()可以选择性的禁止tasklet。这两个函数都增加了tasklet描述符的count字段,但是后一个函数只有在tasklet函数已经运行的实例结束后才返回,为了重新激活你的tasklet,调用tasklet_enable()。

为了激活tasklet,你应该根据自己tasklet需要的优先级,调用tasklet_schedule()函数或tasklet_hi_schedule()函数。这两个函数很类似,其中每个都执行下列操作:

1、检查tasklet_state_sched标志;如果设置则返回。

2、调用local_irq_save保存IF标志的状态并禁用本地中断

3、在tasklet_vec[n]或tasklet_hi_vec[n]指向链表的起始处增加tasklet描述符,n为本地cpu的逻辑号

4、调用raise_softirq_irqoff()激活tasklet_softirq或hi_softirq类型的软中断。

5、调用local_irq_restore恢复IF标志状态。

最后让我们看一下tasklet如何被执行。软中断一旦被激活,就由do_softirq()函数执行。与HI_SOFTIRQ软中断相关的软中断函数叫做tasklet_hi_action(),而与tasklet_softirq相关的函数叫做tasklet_action()。这两个函数非常相似,他们执行下列的操作:

1、禁止本地中断。

2、获得本地cpu的逻辑号n

3、把tasklet_vec[n]或tasklet_hi_vec[n]指向的链表地址存入局部变量list

4、tasklet_vec[n]或tasklet_hi_vec[n]的值赋值为NULL,因此已调度的tasklet描述符的指针链表被清空。

5、打开本地中断。

6、对于list指向的链表中的每个tasklet描述符

a:在多处理器系统上,检查tasklet的tasklet_state_run标志。如果该标志被设置说明同一个类型的tasklet正在另一个cpu上运行,因此就把任务描述符重新插入到由tasklet_vec[n]或tasklet_hi_vec[n]指向的链表中,并再次激活tasklet_softirq或hi_softirq的软中断。这样,当同类型的其他tasklet在在其他cpu上运行时,这个tasklet就被延迟。如果tasklet_state_run标志未设置,tasklet未在其他cpu上运行,就需要设置这个标志,禁止同类tasklet在其他cpu上运行。

b:通过查看tasklet的count字段,检查tasklet是否被禁止。如果是,就清除tasklet_state_run标志,并把任务描述符重新插入到由tasklet_vec[n]或tasklet_hi_vec[n]指向的链表中,然后函数再次激活tasklet_softirq或hi_softirq软中断。

c:如果tasklet被激活,清tasklet_state_sched标志,执行tasklet函数

 

除非tasklet函数重新激活自己,否则,tasklet的每次激活至多tasklet函数的一次执行。

 

原创粉丝点击