(一):下半部
下半部的任务就是执行与中断处理密切相关但中断处理程序本身不执行的工作.那么有一些提示可以借鉴哪些工作放在上半部中执行,哪些工作放在下半部执行.
1:如果一个任务对时间非常敏感,将其放在中断处理程序中进行
2:如果一个任务与硬件相关,将其放在中断处理程序中进行
3:如果一个任务保证不被其他中断打断,将其放在中断处理程序中进行
4:其他所有任务,考虑放在下半部执行
1:为什么要用下半部
我们希望的是尽快减少中断处理程序需要完成的工作量,因为他在运行的时候,当前中断线在所有处理器上都会被屏蔽.更糟糕的是,如果一个中断处理程序是IRQF_DISABLE的类型,他执行的时候会禁止所有本地中断(而且把本地中断线全局的屏蔽掉).而缩短中断被屏蔽的时间对系统的响应能力和性能都至关重要.再加上中断处理程序与其他程序异步执行,所以,我们必须尽力缩短中断处理程序的执行.
2:下半部的环境
和上半部只能通过中断处理程序实现不同,下半部可以通过多种机制实现.这些用来实现下半部的机制分别由不同的接口和子系统组成.
1):”下半部”的起源
最早的linux只提供”botton half”这种机制用于实现下半部,也被称作”BH”.BH提供了一个静态创建,有32个bottom halves组成的链表.上半部通过一个32位整数中的一位来标识出哪个bottom half可以执行.每个BH都在全局范围内进行同步.即使分属于不同的处理器,也不允许任何两个bottom half同时执行.这种机制使用方便却不够灵活,简单却有性能瓶颈.
2):任务队列
后来,任务对来代替BH机制来实现下半部执行的工作.内核定义了一组队列,其中每个队列都包含一个有等待调用的函数组成的链表,根据其所在的位置,这些函数会在某个时刻执行.驱动程序可以把他们自己的下半部注册到合适的队列上.
3):软中断和tasklet
在2.3版本中,内核引入了软中断和tasklet.软中断是一组静态定义的下半部接口,有32个,可以在所有处理器上同时执行--及时两个类型相同也可以.tasklet是一种基于软中断实现的灵活性强,动态创建的下半部实现机制,两个不同类型的tasklet可以在不同的处理器上同时执行,但类型相同的tasklet不能同时执行.tasklet其实是一种在性能和易用性之间寻求平衡的产物.对于大部分下半部处理来说,用tasklet就够了.像网络这样对性能要求非常高的情况才需要使用软中断.但是,使用软中断需要小心,因为两个相同的软中断有可能同时执行.同时,软中断必须在编译期间就进行静态注册,但是tasklet可以通过代码动态注册.
在中间的发展过程中,一直到2.6版本,内核提供了三种不同形式的下半部实现机制:软中断,tasklet和工作队列.
注意,内核定时器也可以实现将工作推后执行.内核定时器把操作推迟到某个确定的时间段之后执行.在刚刚的三种机制中,也需要使用内核定时器来确定推迟的时间段.
(二)软中断
首先需要指明的是,软中断为于kernel/softirq.c
1:软中断的实现
软中断在编译期间是静态分配的.她不像tasklet那样能够动态的注册或者是注销.软中断由softirq_action结构表示,这个结构定义在
struct softirq_action
{
void (*action)(struct softirq_action *);
};
kernel/siftirq.c中定义了一个包含有32个该结构体的数组.
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
每个注册的软中断都占据该数组的一项,因此,最多可能有32个软中断.在2.6.34中,这32项只用到其中的9项.
1):软中断处理程序
软中断处理程序action的函数原型如下:
void softirq_handler(struct softirq_action*)
当内核执行一个软中断处理函数的时候,他就会执行这个action函数,其唯一的参数就是执行这个软中断的softirq_action结构体的指针.例如,如果my_softirq指向softirqvec数组中的某一项,则内核会调用如下方法来调用软中断处理程序中的函数.
my_softirq->action(my_softirq);
在这里传递整个结构体而不是传递数值有一些本身的优点,可以保证将来在结构体中加入新的域的时候,无须对所有的软中断处理程序都进行变动.如果需要,软中断处理程序可以方便的解析他们的参数,从数据成员中提取数值.
一个软中断不会抢占另外一个软中断,实际上,唯一可以抢占软中断的就是中断处理程序.不过,其他的软中断可以在其他处理器上同时运行.
2):执行软中断
一个注册的软中断必须在被标记之后才会执行.这被称作出发软中断.通常,中断处理程序会在返回前标记他的软中断,使其在稍后执行.于是,在合适的时刻,软中断就会运行.在下列地方,待处理的软中断会被检查和执行.
?1:从一个硬件中断代码处返回时
?2:在ksoftirq内核线程中 ?
?3:在那些显示检查和执行待处理的软中断的代码中,如网络子系统中.不管是用什么办法唤起,软中断都要在do_softirq()中执行.这个函数比较简单,如果有带运行的软中断,do_softirq()会循环遍历每一个,调用他们的处理程序.
下面看一下do_softirq()函数的实现过程.
asmlinkage void __do_softirq(void)
{
//软中断结构体
struct softirq_action *h;
//保存待处理的软中断的32位位图
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
//获取当前待处理的软中断的32位位图
pending = local_softirq_pending();
account_system_vtime(current);
__local_bh_disable((unsigned long)__builtin_return_address(0));
lockdep_softirq_enter();
cpu = smp_processor_id();
restart:
/* 由于当前位图已经保存下来了,所以可以允许中断了*/
/* 需要在允许中断之前,重设待处理的位图 */
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);
//允许中断
local_irq_enable();
/* 使h指向数组的第一个 */
h = softirq_vec;
do {
if (pending & 1) {
int prev_count = preempt_count();
kstat_incr_softirqs_this_cpu(h - softirq_vec);
trace_softirq_entry(h, softirq_vec);
//执行中断处理函数
h->action(h);
trace_softirq_exit(h, softirq_vec);
if (unlikely(prev_count != preempt_count())) {
printk(KERN_ERR "huh, entered softirq %td %s %p"
"with preempt_count %08x,"
" exited with %08x?\n", h - softirq_vec,
softirq_to_name[h - softirq_vec],
h->action, prev_count, preempt_count());
preempt_count() = prev_count;
}
rcu_bh_qs(cpu);
}
//移动到下一个位置,接着进行判断
h++;
//同时位图也要移动一个位置
pending >>= 1;
//由于位图是32位的,数组长度也是32,所以他们是一一对应的.
} while (pending);
//禁止中断
local_irq_disable();
//获取当前待处理的软中断的32位位图
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;
if (pending)
wakeup_softirqd();
lockdep_softirq_exit();
account_system_vtime(current);
_local_bh_enable();
}
#ifndef __ARCH_HAS_DO_SOFTIRQ
asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
if (in_interrupt())
return;
local_irq_save(flags);
//获取当前待处理的软中断的32位位图
pending = local_softirq_pending();
//如果有待处理的软中断,也就是有被标记的软中断
//则进行软中断执行
if (pending)
__do_softirq();
local_irq_restore(flags);
}
#endif
这两个函数检查并且执行软中断.具体要做的包括:
?1):用局部变量pending保存local_softirq_pending()宏的返回值.他是待处理的软中断的32位位图.如果第n位被设置为1,那么第n位对应类型的软中断等待处理.
?2):现在待处理的软中断位图已经被保存,可以将实际的软中断位图清零了
?3):将指针h指向softirq_vec的第一项
?4):如果pending的第一位被设置为1,则h->action(h)被调用
?5):指针加1,所以他现在指向softirq_vec数组的第二位
?6):位掩码pending右移一位.这样会丢弃第一位,然后让其他各位以此向右移动一个位置.于是原来在第二位现在就在第一个位置上了.
?7):现在指针h指向数组的第二项,pending位掩码的第二位现在也到了第一位上.重复上面的步骤.
?8):一直重复下去,知道pending变为0,这表明已经没有待处理的软中断了,我们的任务也就完成了.
2:使用软中断
软中断保留给系统中对时间要求最严格以及最重要的下半部使用.目前只有两个子系统(网络和SCSI)直接使用软中断.此外tasklet和内核定时器都是建立在软中断上的.tasklet相对于软中断来说,可以动态生成,对加锁要求不高,性能也不错.
1):分配索引
在编译期间,通过在linux/interrupt.h中定义的一个枚举类型来静态的声明软中断.内核用这些从0开始的索引来表示一种相对优先级.索引号小的软中断在索引号大的软中断之前执行.
建立一个新的软中断必须在此枚举类型中加入新的项.而加入的时候,我们不能像在其他地方一样,简单的把新项加到列表的末尾.相反,你必须根据希望赋予它的优先级来决定加入的位置.习惯上,HI_SOFTIRQ作为第一项,RCU_SOFTIRQ作为第二项.
下面列出已有的tasklet类型.
/* 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,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
};
上面的注释说的是如果不是对时间要求特别高的话,tasklet就足够满足我们的要求了.
2):注册你的处理程序
在运行的时候,通过调用open_softirq()注册软中断处理程序,该函数有两个参数:软中断索引号和处理函数.例如网络子系统,在net/coreldev.c,通过以下方式注册自己的软中断.
open_softirq(NET_TX_SOFTIRQ,net_tx_action);
open_softirq(NET_RX_SOFTIRQ,net_rx_action);
软中断处理程序执行的时候,允许响应中断.但他自己不能休眠.在一个处理程序运行的时候,当前处理器上的软中断被禁止.但是其他的处理器仍可以执行别的软中断.实际上,如果同一个软中断在他被执行的时候再次被触发了,那么另外一个处理器可以同时运行其处理程序.这意味着任何共享数据(甚至是仅在软中断处理程序内部使用的全局变量)都需要严格的锁保护.tasklet只不过是同一个处理程序的多个实例不能在多个处理器上同时运行.
3):触发你的软中断
通过在枚举类型的列表中添加新项以及调用open_softirq()进行注册以后,新的中断处理程序就能够运行.raise_softirq()函数可以将一个软中断设置为挂起状态,让他在下次调用do_softirq()函数的时候投入运行.例如,网络子系统可能会调用:
raise_softirq(NET_TX_SOFTIRQ);
这会触发NET_TX_SOFTIRQ软中断.他的处理程序net_tx_action()就会在内核下一次执行软中断的时候投入运行.该函数在触发一个软中断之前先要禁止中断,触发后再恢复到原来的状态.如果中断本来就已经禁止了,那么可以调用另一个函数raise_softirq_irqoff(),这会带来一些优化效果.
/*
* 中断已经禁止
*/
raise_softirq_irqoff(NET_TX_SOFTIRQ);
在中断处理程序中触发软中断是最常见的形式.在这种情况下,中断处理程序执行硬件设备的相关操作,然后触发相应的软中断,最后退出.内核在执行完中断处理程序之后,马上就会调用do_softirq()函数.于是软中断开始执行中断处理程序留给他去完成的剩余任务.
版权声明:您好,转载请留下本人博客的地址,谢谢
原文地址:http://blog.csdn.net/hongbochen1223/article/details/47834089