现在的位置: 首页 > 综合 > 正文

linux中断控制之软中断

2018年01月10日 ⁄ 综合 ⁄ 共 4372字 ⁄ 字号 评论关闭
软中断给系统中对时间要求最严格的下半部使用,执行频率很高,连续性很高,网络和SCSI直接使用软中断。
此外,内核定时器和tasklet都是建立在软中断上的。
软中断由softirq_action结构表示,定义于#include<linux/interrupt.h>  内核3.1.4
struct softirq_action
{
void
(*action)(struct softirq_action *);
};
软中断的源码位于 kernel/softirq.c 中。
分配索引
在编译期间,可以通过<linux/interrupt.h>中定义的一个枚举类型来静态声明软中断。索引号小的软中断在索引号大的软中断之前执行。要添加新项的话根据赋予它的优先级来决定加入的位置而不是直接添加到列表末尾
/* 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,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
软中断处理程序
当内核运行一个软中断处理程序的时候,它就会执行这个action函数,其唯一的参数就是指向对应的softirq_action结构体的指针。
为什么要传这个结构体而不是传数据值呢?
这样做的原因是可以保证将来在结构体中加入新的域时,无须对所有的软中断处理程序进行变动。如果需要,软中断处理程序可以方便地解析它的参数,从数据成员中提取数值。
调用open_softirq()注册软中断处理函数
void open_softirq(int nr, void (*action)(struct softirq_action *))    //2.6的核还有data
{
softirq_vec[nr].action = action;
}
raise_softirq()函数可以将一个软中断设置为挂起状态,让它在下次调用do_softirq()函数时投入运行:
void raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);    //禁止当前处理器上的本地中断,保存中断系统状态
raise_softirq_irqoff(nr);
local_irq_restore(flags);    //激活当前处理器上的本地中断,恢复中断系统状态
}
该raise_softirq()在触发一个软中断之前先要禁止中断,触发后再恢复原来的状态,如果中断本来就已经被禁止了,那么可以调用另一个函数raise_softirq_irqoff():
/*
 * This function must run with irqs disabled!
 */
inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);
/*
 * If we're in an interrupt or softirq, we're done
 * (this also catches softirq-disabled code). We will
 * actually run the softirq once we return from
 * the irq or softirq.
 *
 * Otherwise we wake up ksoftirqd to make sure we
 * schedule the softirq soon.
 */
if (!in_interrupt())
wakeup_softirqd();     //此函数实际是调用 wake_up_process() 来唤醒 ksoftirqd
}
执行软中断
do_IRQ 函数执行完硬件 ISR 后退出时调用此函数
/*
 * Exit an interrupt context. Process softirqs if needed and possible:
 */
void irq_exit(void)
{
account_system_vtime(current);
trace_hardirq_exit();
sub_preempt_count(IRQ_EXIT_OFFSET);
if (!in_interrupt() && local_softirq_pending())    //判断内核没有在中断上下文,并且有软中断在pending状态
invoke_softirq();            //调用do_softirq()函数
rcu_irq_exit();
#ifdef CONFIG_NO_HZ
/* Make sure that timer wheel updates are propagated */
if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
tick_nohz_stop_sched_tick(0);
#endif
preempt_enable_no_resched();
}
软中断要在 do_softirq()函数中执行
asmlinkage void do_softirq(void)
{
__u32 pending;    //处理32个软中断
unsigned long flags;
if (in_interrupt())    
return;    //用来判断内核是否处于中断上下文中,如果是,它返回非零,说明内核此刻正在执行中断处理程序,或者正                    在执行下半部处理程序。
local_irq_save(flags);
pending = local_softirq_pending();    //待处理的32位软中断位图,如果第n为被设置为1,那么第n位对应类型的软中断等待处理。
if (pending)
__do_softirq();    //函数__do_softirq
local_irq_restore(flags);
}
#define MAX_SOFTIRQ_RESTART 10      //最大软中断调用次数为 10 次
asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
pending = local_softirq_pending();    //得到当前所有pending的软中断
account_system_vtime(current);
__local_bh_disable((unsigned long)__builtin_return_address(0),
SOFTIRQ_OFFSET);    //屏蔽其他软中断,这里也就证明了每个 CPU 上同时运行的软中断只能一个
lockdep_softirq_enter();
cpu = smp_processor_id();    //得到当前正在处理的cpu
restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);
local_irq_enable();     //这部分是软中断处理的核心
                                            //硬中断可以在这抢占了
h = softirq_vec;    //将指针h指向softirq_vec的第一项,即得到软中断向量表
do {
if (pending & 1) {
unsigned int vec_nr = h - softirq_vec;
int prev_count = preempt_count();
kstat_incr_softirqs_this_cpu(vec_nr);
trace_softirq_entry(vec_nr);
h->action(h);        //一个软中断不会抢占另外一个软中断,唯一可以抢占软中断的是中断处理程序
trace_softirq_exit(vec_nr);
if (unlikely(prev_count != preempt_count())) {    //unlikely函数是内核一种很快的比较函数
printk(KERN_ERR "huh, entered softirq %u %s %p"
       "with preempt_count %08x,"
       " exited with %08x?\n", vec_nr,
       softirq_to_name[vec_nr], h->action,
       prev_count, preempt_count());
preempt_count() = prev_count;
}
rcu_bh_qs(cpu);
}
h++;
pending >>= 1;
} while (pending);
将指针h指向softirq_vec的第一项,如果pending的第一位被置为1,h->action(h)被调用,指针加1,指向数组中的下一项。位掩码pending右移一位,这样会丢弃第一位,然后让其他各位依次向右移动一个位置。一直重复直到pengding变为0,结束。
local_irq_disable();
pending = local_softirq_pending();    //可能被注册了软中断,所以重新取得所有软中断
if (pending && --max_restart)
goto restart;
if (pending)
wakeup_softirqd();    //此函数实际是调用 wake_up_process() 来唤醒 ksoftirqd
lockdep_softirq_exit();
account_system_vtime(current);
__local_bh_enable(SOFTIRQ_OFFSET);    //不是local_bh_enable(),不会再次触发do_softirq()
}
有兴趣的可以看看ksoftirqd。

 

 

抱歉!评论已关闭.