上两篇分析了I/O中断处理过程,接下来我们来分析softirq和tasklet
一、softirq
中断处理一般分为三部分:第一是critical,第二是Noncritical,第三是Noncritical deferrable,softirq就经常用于Noncritical deferrable阶段
1、优点:
1、softirq执行期间,中断处于使能
2、将其从中断处理函数中去除,可以减小kernel对中断反应时间,这对中断反应时间要求高的中断请求很重要
2、特点:
1、静态分配
2、必须具有可重入性,在多核系统中,几个CPU可以同时运行同种softirq
3、操作种类
1、初始化softirq
2、激活softirq
3、Mask softirq
4、处理softirq
4、Linux2.6 softirq种类
softirq | index | Description |
HI_SOFTIRQ | 0 | Handles high priority tasklets |
TIMER_SOFTIRQ | 1 | Tasklets related to timer interrupts |
NET_TX_SOFTIRQ | 2 | Transmits packets to network cards |
NET_RX_SOFTIRQ | 3 | Receives packets from network cards |
SCSI_SOFTIRQ | 4 | Post-interrupt processing of SCSI commands |
TASKLET_SOFTIRQ | 5 | Handles regular tasklets |
5、重要数据类型
1、struct softirq_action
struct softirq_action
{
void (*action)(struct softirq_action *);
void *data;
};
static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp
注意:这里虽然定义了32个,但是其实只用了0-5前面六个,index越低,优先级越高
2、preempt_count
struct thread_info {
struct task_struct *task; /* main task structure */
struct exec_domain *exec_domain; /* execution domain */
unsigned long flags; /* low level flags */
unsigned long status; /* thread-synchronous flags */
__u32 cpu; /* current CPU */
__s32 preempt_count; /* 0 => preemptable, <0 => BUG */
mm_segment_t addr_limit; /* thread address space:
0-0xBFFFFFFF for user-thead
0-0xFFFFFFFF for kernel-thread
*/
struct restart_block restart_block;
unsigned long previous_esp; /* ESP of the previous stack in case
of nested (IRQ) stacks
*/
__u8 supervisor_stack[0];
};
preempt_count位域说明:
bit[7 : 0] preemption counter 0代表可抢占,大于0代表禁止抢占
bit[15 : 8] softirq counter 0代表可延迟函数使能,大于0代表可延迟函数禁止
bit[27 : 16] hardirq counter 代表硬件中断嵌套层次,在irq_enter()和irq_exit()分别增加和减少
bit28 preempt_active flag
3、irq_cpustat_t
typedef struct {
unsigned int __softirq_pending;
unsigned long idle_timestamp;
unsigned int __nmi_count; /* arch dependent */
unsigned int apic_timer_irqs; /* arch dependent */
} ____cacheline_aligned irq_cpustat_t;
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned
注意:这是一个per CPU类型
6、代码分析
1、open_softirq
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
}
EXPORT_SYMBOL(open_softirq);
根据softirq种类,来填充softirq_vec数组
2、 raise_softirq
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned
#ifndef __ARCH_IRQ_STAT
extern irq_cpustat_t irq_stat[]; /* defined in asm/hardirq.h */
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
#endif
/* arch independent irq_stat fields */
#define local_softirq_pending() \
__IRQ_STAT(smp_processor_id(), __softirq_pending)
#define __raise_softirq_irqoff(nr) do { local_softirq_pending() |= 1UL << (nr); } while (0)
/*
* This function must run with irqs disabled!
*/
inline fastcall 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();
}
EXPORT_SYMBOL(raise_softirq_irqoff);
void fastcall raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
首先关闭local中断,然后调用raise_softirq_irqoff(),设置irq_stat数组中当前CPU对应的irq_cpustat_t中__softirq_pending字段对应的bit位, 判断是否在中断上下文(包括软中断和硬中断),如果不在,则唤醒ksoftirqd守护线程
3、do_softirq
asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
if (in_interrupt())
return;
local_irq_save(flags);
pending = local_softirq_pending();
if (pending)
__do_softirq();
local_irq_restore(flags);
}
EXPORT_SYMBOL(do_softirq);
1)首先判断是否在中断上下文,因为如果在中断上下文,则说明可能正在处理softirq,这种情况是需要禁止的
2)获取irq_stat数组中当前CPU对应的irq_cpustat_t中__softirq_pending字段
3)判断是否有pending的softirq,如果有,则调用__do_softirq
4、__do_softirq
*
* We restart softirq processing MAX_SOFTIRQ_RESTART times,
* and we fall back to softirqd after that.
*
* This number has been established via experimentation.
* The two things to balance is latency against fairness -
* we want to handle softirqs as soon as possible, but they
* should not be able to lock up the box.
*/
#define MAX_SOFTIRQ_RESTART 10
asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
pending = local_softirq_pending();
local_bh_disable();
cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
local_softirq_pending() = 0;
local_irq_enable();
h = softirq_vec;
do {
if (pending & 1) {
h->action(h);
rcu_bh_qsctr_inc(cpu);
}
h++;
pending >>= 1;
} while (pending);
local_irq_disable();
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;
if (pending)
wakeup_softirqd();
__local_bh_enable();
}
1) 获取irq_stat数组中当前CPU对应的irq_cpustat_t中__softirq_pending字段到pending临时变量后,并它清零
2)找到pending中被置1的bit,逐个调用注册的softirq_action->action(),处理各个softirq
3) 判断是否有新的softirq产生,如果有,则继续处理
4)为了防止一直有softirq产生,kernel一直处理softirq,而导致用户程序被长时间挂起,所以这里限定最大次数为10次,如果10次后,依然有softirq产生,则唤醒ksoftirqd守护线程,留到下次处理
5、ksoftirqd
static DEFINE_PER_CPU(struct task_struct *, ksoftirqd)
p = kthread_create(ksoftirqd, hcpu, "ksoftirqd/%d", hotcpu)
static int ksoftirqd(void * __bind_cpu)
{
set_user_nice(current, 19);
current->flags |= PF_NOFREEZE;
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
if (!local_softirq_pending())
schedule();
__set_current_state(TASK_RUNNING);
while (local_softirq_pending()) {
/* Preempt disable stops cpu going offline.
If already offline, we'll be on wrong CPU:
don't process */
preempt_disable();
if (cpu_is_offline((long)__bind_cpu))
goto wait_to_die;
do_softirq();
preempt_enable();
cond_resched();
}
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
wait_to_die:
preempt_enable();
/* Wait for kthread_stop */
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
}
/*
* we cannot loop indefinitely here to avoid userspace starvation,
* but we also don't want to introduce a worst case 1/HZ latency
* to the pending events, so lets the scheduler to balance
* the softirq load for us.
*/
static inline void wakeup_softirqd(void)
{
/* Interrupts are disabled: no need to stop preemption */
struct task_struct *tsk = __get_cpu_var(ksoftirqd);
if (tsk && tsk->state != TASK_RUNNING)
wake_up_process(tsk);
}
1、将ksoftirqd定义per CPU类型
2、通过kthread_create()创建ksoftirq线程,并注册回调函数ksoftirqd
3、ksoftirq将nice设置为19,可见这个优先级是比较低的,然后判断是否有softirq产生,如果没有,则睡眠
4、等醒来时,判断是否有softirq,如果没有,则继续睡眠,如果有,则调用do_softirq()
6、softirq检测场合
1、调用local_bh_enable( )
2、当处理完I/O中断时,调用irq_exit()
3、唤醒ksoftirqd守护线程
4、在多核系统中,各CPU间中断退出时,调用irq_exit()
5、如果系统中使用I/O APIC,smp_apic_timer_interrupt()结束时,调用irq_exit()