中断处理程序下半部软中断tasklet工作队列.docx
- 文档编号:8688518
- 上传时间:2023-02-01
- 格式:DOCX
- 页数:26
- 大小:36.11KB
中断处理程序下半部软中断tasklet工作队列.docx
《中断处理程序下半部软中断tasklet工作队列.docx》由会员分享,可在线阅读,更多相关《中断处理程序下半部软中断tasklet工作队列.docx(26页珍藏版)》请在冰豆网上搜索。
中断处理程序下半部软中断tasklet工作队列
中断处理程序下半部
1.下半部综述
1.1.使用下半部的目的
linux将中断处理程序分为上半部和下半部,目的是尽量减少上半部需要完成的工作,因为在上半部执行的时候,当前的中断线在所有处理器上都会被屏蔽。
而且,如果一个处理程序是IRQF_DISABLED类型,它执行的时候会禁止所有本地中断。
而缩短中断被屏蔽的时间对系统的响应能力和性能都至关重要。
因此需要把一些工作放到下半部去做。
不仅是Linux,许多操作系统也把处理硬件中断的过程分为两个部分。
上半部分简单快速,执行的时候禁止一些或全部中断。
下半部分稍后执行,而且执行期间可以响应所有的中断。
这种设计可使系统处于中断屏蔽状态的时间尽可能的短,以此来提高系统的响应能力。
1.2.Linux中的下半部发展
和上半部只能通过中断处理程序实现不同,下半部可以通过多种机制实现。
最早的Linux只提供“bottomhalf”这种机制用于实现下半部。
它提供了一个静态创建、由32个bottomhalves组成的链表。
上半部通过一个32位整数中的一个位来标识出哪个bottomhalf可以执行。
每个BH都在全局范围内进行同步。
即使分属于不同的处理器,也不允许任何两个bottomhalf同时执行。
不久,内核开发者们就引入了任务队列机制来实现工作的推后执行,并用它来代替BH机制。
内核为此定义了一组队列,其中每个队列都包含一个由等待调用的函数组成链表。
根据其所处队列的位置,这些函数会在某个时刻执行。
驱动程序可以把他们自己的下半部注册到合适的队列上去。
在2.3这个开发版中,引入了软中断和tasklet。
软中断是一组静态定义的下半部接口,有32个,可以在所有处理器上同时执行——即使两个类型相同也可以。
两个不同类型的tasklet可以在不同的处理器上同时执行,但类型相同的tasklet不能同时执行。
tasklet其实是一种在性能和易用性之间寻求平衡的产物。
对于大部分下半部处理来说,用tasklet就足够了,像网络这样对性能要求非常高的情况下才需要使用软中断。
可是,使用软中断需要特别小心,因为两个相同的软中断有可能同时被执行。
此外,软中断还必须在编译期间就进行静态注册。
与此相反,tasklet可以通过代码进行动态注册。
另外一个可以用于将工作推后执行的机制是内核定时器。
内核定时器把操作推迟到某个确定的时间段之后执行。
也就是说,当你必须保证在一个确定的时间段过去以后再执行时,你应该使用内核定时器。
工作队列是另外一种将工作推后执行的形式,工作队列可以把工作推后,交由一个内核线程去执行——这个下半部分总是会在进程上下文中执行。
这样,通过工作队列执行的代码能占尽进程上下文的所有优势。
最重要的就是工作队列允许重新调度甚至是睡眠。
1.
2.软中断和tasklet的关系
tasklet是利用软中断实现的一种下半部机制。
tasklet是通过软中断实现的,所以tasklet本身也是软中断。
tasklet由两类软中断代表:
HI_SOFTIRQ和TASKLET_SOFTIRQ。
2.1.软中断列表
tasklet类型列表
tasklet
优先级
软中断描述
HI_SOFTIRQ
0
优先级高的tasklets
TIMER_SOFTIRQ
1
定时器的下半部
NET_TX_SOFTIRQ
2
发送网络数据包
NET_RX_SOFTIRQ
3
接收网络数据包
BLOCK_SOFTIRQ
4
BLOCK装置
TASKLET_SOFTIRQ
5
正常优先权的tasklets
SCHED_SOFTIRQ
6
调度程度
HRTIMER_SOFTIRQ
7
高分辨率定时器
RCU_SOFTIRQ
8
RCU锁定
2.2.注册tasklet
在kernel/softirq.c通过以下方式注册tasklet:
open_softirq(TASKLET_SOFTIRQ,tasklet_action);
open_softirq(HI_SOFTIRQ,tasklet_hi_action);
tasklet_action()和tasklet_hi_action()在kernel/softirq.c定义
2.3.触发软中断
一个注册的软中断必须在被标记后才会执行。
这被称作触发软中断(raisingthesoftirq)。
通常,中断处理程序会在返回前标记它的软中断,使其在稍后被执行。
在下列地方,待处理的软中断会被检查和执行:
●从一个硬件中断代码返回时
●在ksoftirqd内核线程中
●在那些显示检查和执行待处理的软中断的代码中,如网络子系统中
2.4.执行软中断
不管是用什么办法唤起软中断,它都要在do_softirq()中执行。
该函数很简单,如果有待处理的软中断,do_softirq()会循环遍历每一个,调用他们的处理程序。
下面是do_softirq()简化后的核心部分:
u32pending;
pending=local_softirq_pending();
if(pending){
structsoftirq_action*h;
set_softirq_pending(0);//重设待处理的位图
h=softirq_vec;
do{
if(pending&1)
h->action(h);
h++;
pending>>=1;
}while(pending);
}
2.5.pending位图与softirq_vec数组的关系
下面是一个pending位图与softirq_vec数组的关系
u32pending:
staticstructsoftirq_actionsoftirq_vec[NR_SOFTIRQS]:
NULL
NULL
…
action8
action7
action6
tasklet_action
action4
net_rt_action
net_tx_action
action1
tasklet_hi_atcion
3.软中断
3.1.软件中断的数据结构
3.1.1.structsoftirq_action
内核用softirq_action完成软中断的注册和激活等操作,它的定义如下:
struct softirq_action
{
void (*action)(struct softirq_action *);
};
structsoftirq_action
{
void(*action)(structsoftirq_action*);
};
非常简单,只有一个用于回调的函数指针。
软件中断的资源是有限的,内核目前只实现了10种类型的软件中断,它们是:
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
内核的开发者们不建议我们擅自增加软件中断的数量,如果需要新的软件中断,尽可能把它们实现为基于软件中断的tasklet形式。
与上面的枚举值相对应,内核定义了一个softirq_action的结构数组,每种软中断对应数组中的一项:
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp; staticstructsoftirq_actionsoftirq_vec[NR_SOFTIRQS]__cacheline_aligned_in_smp;
1.
2.
3.
3.1.
3.1.1.
3.1.2.irq_cpustat_t
多个软中断可以同时在多个cpu运行,就算是同一种软中断,也有可能同时在多个cpu上运行。
内核为每个cpu都管理着一个待决软中断变量(pending),它就是irq_cpustat_t:
typedef struct {
unsigned int __softirq_pending;
} ____cacheline_aligned irq_cpustat_t;
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
__softirq_pending字段中的每一个bit,对应着某一个软中断,某个bit被置位,说明有相应的软中断等待处理。
1.
2.
3.
3.1.
3.2.软中断的守护进程ksoftirqd
在cpu的热插拔阶段,内核为每个cpu创建了一个用于执行软件中断的守护进程ksoftirqd,同时定义了一个per_cpu变量用于保存每个守护进程的task_struct结构指针:
DEFINE_PER_CPU(struct task_struct *, ksoftirqd);
DEFINE_PER_CPU(structtask_struct*,ksoftirqd);
大多数情况下,软中断都会在irq_exit阶段被执行,在irq_exit阶段没有处理完的软中断才有可能会在守护进程中执行。
3.3.触发软中断
要触发一个软中断,只要调用api:
raise_softirq即可,它的实现很简单,先是关闭本地cpu中断,然后调用:
raise_softirq_irqoff
void raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
voidraise_softirq(unsignedintnr)
{
unsignedlongflags;
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
再看看raise_softirq_irqoff:
inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);
......
if (!
in_interrupt())
wakeup_softirqd();
}
inlinevoidraise_softirq_irqoff(unsignedintnr)
{
__raise_softirq_irqoff(nr);
......
if(!
in_interrupt())
wakeup_softirqd();
}
先是通过__raise_softirq_irqoff设置cpu的软中断pending标志位(irq_stat[NR_CPUS]),然后通过in_interrupt判断现在是否在中断上下文中,或者软中断是否被禁止,如果都不成立,则唤醒软中断的守护进程,在守护进程中执行软中断的回调函数。
否则什么也不做,软中断将会在中断的退出阶段被执行。
3.4.软中断的执行
基于上面所说,软中断的执行既可以守护进程中执行,也可以在中断的退出阶段执行。
实际上,软中断更多的是在中断的退出阶段执行(irq_exit),以便达到更快的响应,加入守护进程机制,只是担心一旦有大量的软中断等待执行,会使得内核过长地留在中断上下文中。
1.1.1.
1.1.2.
1.1.3.
1.1.4.
1.
2.
2.1.
2.2.
2.3.
2.4.
3.4.1.在irq_exit中执行
看看irq_exit的部分:
void irq_exit(void)
{
......
sub_preempt_count(IRQ_EXIT_OFFSET);
if (!
in_interrupt() && local_softirq_pending())
invoke_softirq();
......
}
voidirq_exit(void)
{
......
sub_preempt_count(IRQ_EXIT_OFFSET);
if(!
in_interrupt()&&local_softirq_pending())
invoke_softirq();
......
}
如果中断发生嵌套,in_interrupt()保证了只有在最外层的中断的irq_exit阶段,invoke_interrupt才会被调用,当然,local_softirq_pending也会实现判断当前cpu有无待决的软中断。
代码最终会进入__do_softirq中,内核会保证调用__do_softirq时,本地cpu的中断处于关闭状态,进入__do_softirq:
asmlinkage void __do_softirq(void)
{
......
pending = local_softirq_pending();
__local_bh_disable((unsigned long)__builtin_return_address(0),
SOFTIRQ_OFFSET);
restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);
local_irq_enable();
h = softirq_vec;
do {
if (pending & 1) {
......
trace_softirq_entry(vec_nr);
h->action(h);
trace_softirq_exit(vec_nr);
......
}
h++;
pending >>= 1;
} while (pending);
local_irq_disable();
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;
if (pending)
wakeup_softirqd();
lockdep_softirq_exit();
__local_bh_enable(SOFTIRQ_OFFSET);
}
首先取出pending的状态;
禁止软中断,主要是为了防止和软中断守护进程发生竞争;
清除所有的软中断待决标志;
打开本地cpu中断;
循环执行待决软中断的回调函数;
如果循环完毕,发现新的软中断被触发,则重新启动循环,直到以下条件满足,才退出:
没有新的软中断等待执行;循环已经达到最大的循环次数MAX_SOFTIRQ_RESTART,目前的设定值时10次;
如果经过MAX_SOFTIRQ_RESTART次循环后还未处理完,则激活守护进程,处理剩下的软中断;
退出前恢复软中断;
3.4.2.在ksoftirqd进程中执行
从前面几节的讨论我们可以看出,软中断也可能由ksoftirqd守护进程执行,这要发生在以下两种情况下:
在irq_exit中执行软中断,但是在经过MAX_SOFTIRQ_RESTART次循环后,软中断还未处理完,这种情况虽然极少发生,但毕竟有可能;
内核的其它代码主动调用raise_softirq,而这时正好不是在中断上下文中,守护进程将被唤醒;
守护进程最终也会调用__do_softirq执行软中断的回调,具体的代码位于run_ksoftirqd函数中,内核会关闭抢占的情况下执行__do_softirq。
4.tasklet
因为内核已经定义好了10种软中断类型,并且不建议我们自行添加额外的软中断,所以对软中断的实现方式,我们主要是做一个简单的了解,对于驱动程序的开发者来说,无需实现自己的软中断。
但是,对于某些情况下,我们不希望一些操作直接在中断的handler中执行,但是又希望在稍后的时间里得到快速地处理,这就需要使用tasklet机制。
tasklet是建立在软中断上的一种延迟执行机制,它的实现基于TASKLET_SOFTIRQ和HI_SOFTIRQ这两个软中断类型。
1.
2.
3.
4.
4.1.tasklet_struct
在软中断的初始化函数softirq_init的最后,内核注册了TASKLET_SOFTIRQ和HI_SOFTIRQ这两个软中断:
void __init softirq_init(void)
{
......
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
void__initsoftirq_init(void)
{
......
open_softirq(TASKLET_SOFTIRQ,tasklet_action);
open_softirq(HI_SOFTIRQ,tasklet_hi_action);
}
内核用一个tasklet_struct来表示一个tasklet,它的定义如下:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
structtasklet_struct
{
structtasklet_struct*next;
unsignedlongstate;
atomic_tcount;
void(*func)(unsignedlong);
unsignedlongdata;
};
next用于把同一个cpu的tasklet链接成一个链表,state用于表示该tasklet的当前状态,目前只是用了最低的两个bit,分别用于表示已经准备被调度执行和已经在另一个cpu上执行:
enum
{
TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */
TASKLET_STATE_RUN /* Tasklet is running (SMP only) */
};
enum
{
TASKLET_STATE_SCHED,/*Taskletisscheduledforexecution*/
TASKLET_STATE_RUN/*Taskletisrunning(SMPonly)*/
};
原子变量count用于tasklet对tasklet_disable和tasklet_enable的计数,count为0时表示允许tasklet执行,否则不允许执行,每次tasklet_disable时,该值加1,tasklet_enable时该值减1。
func是tasklet被执行时的回调函数指针,data则用作回调函数func的参数。
1.
2.
3.
4.
4.1.
4.2.初始化一个tasklet
有两种办法初始化一个tasklet,第一种是静态初始化,使用以下两个宏,这两个宏定义一个tasklet_struct结构,并用相应的参数对结构中的字段进行初始化:
DECLARE_TASKLET(name,func,data);定义名字为name的tasklet,默认为enable状态,也就是count字段等于0。
DECLARE_TASKLET_DISABLED(name,func,data);定义名字为name的tasklet,默认为enable状态,也就是count字段等于1。
第二个是动态初始化方法:
先定义一个tasklet_struct,然后用tasklet_init函数进行初始化,该方法默认tasklet处于enable状态:
struct tasklet_struct tasklet_xxx;
......
tasklet_init(&tasklet_xxx, func, data); structtasklet_structtasklet_xxx;
4.3.tasklet的使用方法
使能和禁止tasklet,使用以下函数:
tasklet_disable() 通过给count字段加1来禁止一个tasklet,如果tasklet正在运行中,则等待运行完毕才返回(通过TASKLET_STATE_RUN标志)。
tasklet_disable_nosync() tasklet_disable的异步版本,它不会等待tasklet运行完毕。
tasklet_enable() 使能tasklet,只是简单地给count字段减1。
调度tasklet的执行,使用以下函数:
tasklet_schedule(structtasklet_struct*t) 如果TASKLET_STATE_SCHED标志为0,则置位TASKLET_STATE_SCHED,然后把tasklet挂到该cpu等待执行的tasklet链表上,接着发出TASKLET_SOFTIRQ
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 中断 处理 程序 下半部软 tasklet 工作 队列
![提示](https://static.bdocx.com/images/bang_tan.gif)