您的位置:首页 > 其它

内核源码分析之软中断(基于3.16-rc4)

2014-07-29 21:14 302 查看
1.和软中断相关的数据结构:

softing_vec数组(kernel/softirq.c)

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;


NR_SOFTIRQS值为10,说明内核支持10个软中断函数。

softirq_action结构体(include/linux/interrupt.h)

struct softirq_action
{
void    (*action)(struct softirq_action *);
};


action是函数指针变量,指向了某个软中断函数。

irq_cpustat_t结构体(arch/x86/include/asm/hardirq.h)

typedef struct {
unsigned int __softirq_pending;
unsigned int __nmi_count;    /* arch dependent */
#ifdef CONFIG_X86_LOCAL_APIC
unsigned int apic_timer_irqs;    /* arch dependent */
unsigned int irq_spurious_count;
unsigned int icr_read_retry_count;
#endif
#ifdef CONFIG_HAVE_KVM
unsigned int kvm_posted_intr_ipis;
#endif
unsigned int x86_platform_ipis;    /* arch dependent */
unsigned int apic_perf_irqs;
unsigned int apic_irq_work_irqs;
#ifdef CONFIG_SMP
unsigned int irq_resched_count;
unsigned int irq_call_count;
/*
* irq_tlb_count is double-counted in irq_call_count, so it must be
* subtracted from irq_call_count when displaying irq_call_count
*/
unsigned int irq_tlb_count;
#endif
#ifdef CONFIG_X86_THERMAL_VECTOR
unsigned int irq_thermal_count;
#endif
#ifdef CONFIG_X86_MCE_THRESHOLD
unsigned int irq_threshold_count;
#endif
#if IS_ENABLED(CONFIG_HYPERV) || defined(CONFIG_XEN)
unsigned int irq_hv_callback_count;
#endif
} ____cacheline_aligned irq_cpustat_t;


每个cpu都有一个这样的结构体变量,在软中断中,我们要使用的是第2行的成员,32位的软中断掩码。当有一个软中断被挂起(将要被执行)的时候,会设置该掩码中的相应位。

2.软中断的执行过程

首先使用open_softirq()函数注册软中断函数,代码如下(kernel/softirq.c):

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}


将软中断函数指针action存入softirq_vec数组的对应元素中。

接着,使用raise_softirq()激活软中断,代码如下(kernel/softirq.c):

void raise_softirq(unsigned int nr)
{
unsigned long flags;

local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}


第5行关闭本地中断,第7行恢复中断。第6行激活nr所对应的软中断函数。接着分析 raise_softirq_irqoff(),代码如下(kernel/softirq.c):

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();
}


第3行__raise_softirq_irqoff函数设置了软中断掩码的相应位,代码如下(kernel/softirq.c)。然后第14行判断软中断是否已经激活或者被禁用,如果没有,那么在15行激活内核线程ksoftirq,去执行软中断。

void (unsigned int nr)
{
trace_softirq_raise(nr);
or_softirq_pending(1UL << nr);
}


具体而言,在第4行的函数中设置掩码位。

3.下面分析下在哪些地方都可以进入软中断。

第一个地方,当然就是上边所提到的,内核线程ksoftirq被激活的时候。下面看看ksoftirq线程(kernel/softirq.c)。

DEFINE_PER_CPU(struct task_struct *, ksoftirqd);


给每个cpu都定义一个指向struct task_struct类型的结构体变量,很显然,该变量存的是ksoftirq线程的进程描述符。(由此也说明,linux的线程和进程是一个东西)

接着,我们要看看ksoftirq线程要执行的函数(kernel/softirq.c)。

static void run_ksoftirqd(unsigned int cpu)
{
local_irq_disable();
if (local_softirq_pending()) {
/*
* We can safely run softirq on inline stack, as we are not deep
* in the task stack here.
*/
__do_softirq();
rcu_note_context_switch(cpu);
local_irq_enable();
cond_resched();
return;
}
local_irq_enable();
}


该函数就是ksoftirq线程的线程体。第9行__do_softirq()中调用所有软中断函数。

我们回过头来再分析下wakeup_softirqd(),看看ksoftirq线程怎样被唤醒(kernel/softirq.c)。

static void wakeup_softirqd(void)
{
/* Interrupts are disabled: no need to stop preemption */
struct task_struct *tsk = __this_cpu_read(ksoftirqd);

if (tsk && tsk->state != TASK_RUNNING)
wake_up_process(tsk);
}


第4行把本地cpu的ksoftirq线程的描述符读到tsk变量中,第6行中判断ksoftirq线程如果没有运行的话,第7行唤醒该线程。

第二个地方,中断处理程序do_IRQ完成处理或者调用irq_exit函数时。下面看看irq_exit代码(kernel/softirq.c)。

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_sub(HARDIRQ_OFFSET);
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();

tick_irq_exit();
rcu_irq_exit();
trace_hardirq_exit(); /* must be last! */
}


不用我说了吧,我觉得你一眼就能瞄见了第12行。看下invoke_softirq函数(kernel/softirq.c)。

static inline void invoke_softirq(void)
{
if (!force_irqthreads) {
#ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK
/*
* We can safely execute softirq on the current stack if
* it is the irq stack, because it should be near empty
* at this stage.
*/
__do_softirq();
#else
/*
* Otherwise, irq_exit() is called on the task stack that can
* be potentially deep already. So call softirq in its own stack
* to prevent from any overrun.
*/
do_softirq_own_stack();
#endif
} else {
wakeup_softirqd();
}
}


第3行force_irqthreads值为0,所以该函数也调用了__do_softirq()来执行软中断。

还有几处地方暂时不分析了,以后有空补上。

4.下面来看下__do_softirq()函数(kernel/softirq.c)。

asmlinkage __visible void __do_softirq(void)
{
unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
unsigned long old_flags = current->flags;
int max_restart = MAX_SOFTIRQ_RESTART;
struct softirq_action *h;
bool in_hardirq;
__u32 pending;
int softirq_bit;

/*
* Mask out PF_MEMALLOC s current task context is borrowed for the
* softirq. A softirq handled such as network RX might set PF_MEMALLOC
* again if the socket is related to swap
*/
current->flags &= ~PF_MEMALLOC;

pending = local_softirq_pending();
account_irq_enter_time(current);

__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
in_hardirq = lockdep_softirq_start();

restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);

local_irq_enable();

h = softirq_vec;

while ((softirq_bit = ffs(pending))) {
unsigned int vec_nr;
int prev_count;

h += softirq_bit - 1;

vec_nr = h - softirq_vec;
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())) {
pr_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_set(prev_count);
}
h++;
pending >>= softirq_bit;
}

rcu_bh_qs(smp_processor_id());
local_irq_disable();

pending = local_softirq_pending();
if (pending) {
if (time_before(jiffies, end) && !need_resched() &&
--max_restart)
goto restart;

wakeup_softirqd();
}

lockdep_softirq_end(in_hardirq);
account_irq_exit_time(current);
__local_bh_enable(SOFTIRQ_OFFSET);
WARN_ON_ONCE(in_interrupt());
tsk_restore_flags(current, old_flags, PF_MEMALLOC);
}


在该函数中循环调用的所有的被激活的软中断函数。第5行MAX_SOFTIRQ_RESTART值为10,表示最多循环10次(不能让其他进程等太久),第32行获取pending表中第一被设置的比特位,第44行开始执行设置过的软中断函数。第53行对pending进行右移运算,然后进入下次循环。直到将本轮所有已设置的软中断函数全部执行完,退出循环。第59行重新获得本地cpu的软中断掩码,第61行如果时间没有超出end而且没有出现更高优先级的进程并且10次寻环未用完,那么跳回restart,重新for循环。否则,第65行唤醒softirqd内核线程。然后退出本函数。

至此,软中断的处理过程就分析完了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: