标签:
软中断概念在嵌入式开发可以有两个不同的解释:学习最常用的硬件中断,我们最关心的是中断触发(硬件)-中断分发-中断处理这个流程如何完成,对于软中断我们也需要搞明白这几点。
首先来看下kernel中跟软中断相关的结构体变量,在kernel/softirq.c中,如下:
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp; softirq_vec数组表示kernel下所有注册的软中断。 struct softirq_action { void (*action)(struct softirq_action *); }; 数组下标表示软中断号,softirq_action则代表软中断处理函数。 softirq_vec类似于kernel下表征硬件中断的irq_desc。 /* 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 };kernel下定义了10种软中断,其中包括应用于定时器 tasklet 网络收发(NAPI) 块设备读写等。
softirq注册使用函数open_softirq,类似于硬件中断的register_irq。定义在kernel/softirq.c中
void open_softirq(int nr, void (*action)(struct softirq_action *)) { softirq_vec[nr].action = action; }很简单,添加softirq_vec对应下标的处理函数即可。
void __init init_timers(void) { int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE, (void *)(long)smp_processor_id()); init_timer_stats(); BUG_ON(err != NOTIFY_OK); register_cpu_notifier(&timers_nb); open_softirq(TIMER_SOFTIRQ, run_timer_softirq); }定时器处理函数为run_timer_softirq,后面学习定时器实现还会分析。
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(); } void raise_softirq(unsigned int nr) { unsigned long flags; local_irq_save(flags); raise_softirq_irqoff(nr); local_irq_restore(flags); } void __raise_softirq_irqoff(unsigned int nr) { trace_softirq_raise(nr); or_softirq_pending(1UL << nr); }2个函数区别在于raise_softirq在触发softirq时会屏蔽irq。
硬件中断是异步于处理器,异步于软件程序的。硬件中断触发-中断异常入口-中断分发处理,硬件中断分发处理时机取决于硬件中断的时机,软件不需要控制。
而软中断不能异步触发调用,软中断的分发处理函数是do_softirq,分发处理时机主要有3种:
(1)在处理完一个硬件中断以后。
(2)在ksoftirqd内核线程中。
(3)在那些显式检查和执行待处理的软中断的代码中,如网络子系统中(显式调用do_softirq来收发数据包)。
我们主要来看前2种情况
(1) 硬件中断处理函数退出时
中断处理函数退出时会调用irq_exit,在kernel/softirq.c中如下:
static inline void invoke_softirq(void) { if (!force_irqthreads) { #ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED __do_softirq(); #else do_softirq(); #endif } else { __local_bh_disable((unsigned long)__builtin_return_address(0), SOFTIRQ_OFFSET); wakeup_softirqd(); __local_bh_enable(SOFTIRQ_OFFSET); } } /* * 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()) invoke_softirq(); #ifdef CONFIG_NO_HZ /* Make sure that timer wheel updates are propagated */ if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched()) tick_nohz_irq_exit(); #endif rcu_irq_exit(); sched_preempt_enable_no_resched(); }检查__softirq_pending是否有置位,有则invoke_softirq调用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); }local_softirq_pending获取__softirq_pending软中断状态变量,如果有softirq的pending则调用__do_softirq,如下:
#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(); account_system_vtime(current); __local_bh_disable((unsigned long)__builtin_return_address(0), SOFTIRQ_OFFSET); lockdep_softirq_enter(); cpu = smp_processor_id(); restart: /* Reset the pending bitmask before enabling irqs */ set_softirq_pending(0); local_irq_enable(); 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())) { 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); local_irq_disable(); pending = local_softirq_pending(); if (pending && --max_restart) goto restart; if (pending) wakeup_softirqd(); lockdep_softirq_exit(); account_system_vtime(current); __local_bh_enable(SOFTIRQ_OFFSET); }__do_softirq处理softirq之前获取并清空__softirq_pending,然后根据pending遍历softirq_vec,调用对应pending的action处理函数。
static struct timer_list test_timer; init_timer(&test_timer); test_timer.function = (void *)test_handle_function; test_timer.data = (u32) test; test_timer.expires = CHECK_TIME + jiffies; add_timer(&test_timer);该段代码初始化timer_list各个成员,timer_list代表一个定时器对象,最后将该timer_list添加到内核定时器链表tvec_bases中。
void run_local_timers(void) { hrtimer_run_queues(); raise_softirq(TIMER_SOFTIRQ); }其中调用raise_softirq触发了定时器软中断。
linux kernel软中断及其衍生品-定时器 tasklet的实现
标签:
原文地址:http://blog.csdn.net/skyflying2012/article/details/44922783