linux内核中断管理分为上下半部机制(top half,bottom half)。中断上半部主要关注中断的响应,而把具体需要关注的任务放到中断下半部中来处理,其中网卡MAC控制器中断的处理是使用软中断来进行下半部处理的典型。
硬件中断属于上半部的范畴,而软中断,tasklet和工作队列等为下半部机制。中断处理程序ISR中不允许进行睡眠,同理在软中断中也是不允许进行睡眠。
1、SoftIRQ软中断
软中断是linux内核很早引入的机制。软中断是预留给系统中对时间要求最为严格和最重要的下半部使用的,而且目前驱动中只有块设备和网络子系统使用了软中断。
系统静态定义了若干种软中断类型,并且linux内核开发者不希望用户再扩充新的软中断类型,如有需要,建议使用tasklet机制。
1.1、数据结构
/* 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
};
通过枚举类型来静态声明软中断,并且每一种软中断都使用索引来表示一种相对的优先级,索引号越小,中断优先级越高,并在一轮软中断中得到优先执行。
struct softirq_action
{
void (*action)(struct softirq_action *);
};
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
typedef struct {
unsigned int __softirq_pending;
#ifdef CONFIG_SMP
unsigned int ipi_irqs[NR_IPI];
#endif
} ____cacheline_aligned irq_cpustat_t;
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
1.2、接口说明
/*注册软中断,nr为软中断序号*/
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
/*主动触发一个软中断*/
void raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags); /*关本地中断*/
raise_softirq_irqoff(nr);
local_irq_restore(flags); /*开本地中断*/
}
/*
* 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(); /*唤醒ksoftirqd%u*/
}
raise_softirq和raise_softirq_irqoff差别在于是否关闭本地中断。
void __raise_softirq_irqoff(unsigned int nr)
{
trace_softirq_raise(nr);
or_softirq_pending(1UL << nr);
}
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
#define local_softirq_pending() __IRQ_STAT(smp_processor_id(), __softirq_pending)
#define set_softirq_pending(x) (local_softirq_pending() = (x))
#define or_softirq_pending(x) (local_softirq_pending() |= (x))
raise_softirq:主动设置softirq处于pending状态,如果当前处于非中断上下文时(非硬中断也非软中断上下文)唤醒ksoftirqd%u内核线程来处理软中断事件。
中断退出时,irq_exit()函数会检查当前是否有pending等待的软中断。
/*
* Exit an interrupt context. Process softirqs if needed and possible:
*/
void irq_exit(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
local_irq_disable();
#else
WARN_ON_ONCE(!irqs_disabled());
#endif
account_irq_exit_time(current);
/*preempt_count上退出硬中断*/
preempt_count_sub(HARDIRQ_OFFSET);
if (!in_interrupt()/*非中断上下文*/ && local_softirq_pending()/*存在本地软中断pending*/)
invoke_softirq();
tick_irq