加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

tasklet schedule

(2011-10-06 10:57:53)
标签:

杂谈

分类: linux
Linux的中断处理框架大体为:入口点-->driver安装的中断处理函数-->退出点
通常说Linux下的中断处理分为top和bottom两部分,top部分中断是关闭的,因此一般在这里的代码都会以迅雷不及掩耳盗铃之势做完该做的事,然后打开中断以防止可能的中断丢失。因为要迅雷不及掩耳盗铃,所以大部分耗时的任务交给了bottom部分去处理。bottom阶段中断是打开的,所以有可能被其他中断到来时所打断。
bottom的延后处理,其中之一遍是由tasklet来完成,相对于workqueue这种任务延后机制,tasklet实际上是处在中断上下文中,因为处在空灵世界(不属于任一进程),所以在tasklet中休眠是绝对应该禁止的。
其他的一些bottom方法:


enum
{
        HI_SOFTIRQ=0,
        TIMER_SOFTIRQ,
        NET_TX_SOFTIRQ,
        NET_RX_SOFTIRQ,
        BLOCK_SOFTIRQ,
        TASKLET_SOFTIRQ,       
        SCHED_SOFTIRQ,
        HRTIMER_SOFTIRQ,
        RCU_SOFTIRQ,       

        NR_SOFTIRQS
};
  1. static inline void tasklet_schedule(struct tasklet_struct *t)
  2. {
  3.         if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
  4.                 __tasklet_schedule(t);
  5. }
  1. void __tasklet_schedule(struct tasklet_struct *t)
  2. {
  3.         unsigned long flags;

  4.         local_irq_save(flags);
  5.         t->next = NULL;
  6.         *__get_cpu_var(tasklet_vec).tail = t;
  7.         __get_cpu_var(tasklet_vec).tail = &(t->next);
  8.         raise_softirq_irqoff(TASKLET_SOFTIRQ);
  9.         local_irq_restore(flags);
  10. }

上诉代码依然处在top阶段,不过做了个标记,留待bottom阶段到来时去检查

invoke_softirq()的核心函数为__do_softirq,实现如下:

  1. #define MAX_SOFTIRQ_RESTART 10

  2. asmlinkage void __do_softirq(void)
  3. {
  4.         struct softirq_action *h;
  5.         __u32 pending;
  6.         int max_restart = MAX_SOFTIRQ_RESTART;
  7.         int cpu;

  8.         pending = local_softirq_pending();
  9.         account_system_vtime(current);

  10.         __local_bh_disable((unsigned long)__builtin_return_address(0));
  11.         trace_softirq_enter();

  12.         cpu = smp_processor_id();
  13. restart:
  14.        
  15.         set_softirq_pending(0);

  16.         local_irq_enable();

  17.         h = softirq_vec;

  18.         do {
  19.                 if (pending & 1) {
  20.                         int prev_count = preempt_count();

  21.                         h->action(h);

  22.                         if (unlikely(prev_count != preempt_count())) {
  23.                                 printk(KERN_ERR "huh, entered softirq %td %p"
  24.                                        "with preempt_count x,"
  25.                                        " exited with x?\n", h - softirq_vec,
  26.                                        h->action, prev_count, preempt_count());
  27.                                 preempt_count() = prev_count;
  28.                         }

  29.                         rcu_bh_qsctr_inc(cpu);
  30.                 }
  31.                 h++;
  32.                 pending >>= 1;
  33.         } while (pending);

  34.         local_irq_disable();

  35.         pending = local_softirq_pending();
  36.         if (pending && --max_restart)
  37.                 goto restart;

  38.         if (pending)
  39.                 wakeup_softirqd();

  40.         trace_softirq_exit();

  41.         account_system_vtime(current);
  42.         _local_bh_enable();
  43. }

在上述代码的21和45行打开了可中断的窗口,在该窗口期间执行的代码可以被新的硬件中断所打断。本来软中断的pending字段已经被set_softirq_pending(0)代码所清除,但是新产生的中断有可能重新挂载一些pending的软中断,所以在执行完上次软中断pending字段的snapshot之后,会再次check有没有新的pending的软中断,也就是上述代码的goto restart的目的。

1. tasklet在中断上下文中执行,所以tasklet中的代码不应该导致调度点的产生(如果调度,因为当前代码不属于任何一进程,或者current此时无效,tasklet在一个边缘世界里).而工作线程是在一个内核进程中,所以工作队列的函数当然可以睡眠。    
2.tasklet始终运行在被当初提交它的那个CPU上,但对于工作队列而言,这只是它的缺省行为,当然可以改变它。                                                                                                                              
3.内核代码可以请求工作队列函数的执行延迟给定的时间间隔。                                                                                                                                                                          
                                                                                                                                                                                                                                  
我们说,深入内核代码的最终目的,一是学习内核代码的设计思想,另一个重要目的是在写我们自己的代码时,因为能清楚了解各种内核设施的背后机制,因而可以能选择最佳的解决方案:你应该选择tasklet还是workqueue。                            
                                                                                                                                                                                                                                  
在海豚看来,在绝大多数的实际使用中,tasklet和workqueue之间的后两点区别并不重要,因为不管在哪个CPU上运行,你的代码总是会被执行。除非你的项目要非常精确地对性能进行调整,此外给请求队列函数的执行指定给定的时间间隔现实中也很少用到。
                                                                                                                                                                                                                                  
它们之间最关键的区别应该是第一点,这点决定了tasklet的代码应该是短小精悍,而且对系统的开销也小(无需象workqueue那样生成一个新的进程),并且它不能睡眠。workqueue则正好相反。                                                        
                                                                                                                                                                                                                                 
海豚曾经遇到过在tasklet中调用一个对外围spi设备寄存器读写函数,类似spi_reg_read/write云云,如果在tasklet中调用这个函数,系统必然crash掉。因为spi_reg_read/write的内部实现使用了中断驱动,阻塞I/O的实现机制,因此睡眠是必不可少的。此种情况,你别无选择,只能使用workqueue!

 

 

在ARM平台上,irq_stat定义如下为extern irq_cpustat_t irq_stat[];

  1. typedef struct {
  2.         unsigned int __softirq_pending;
  3.         unsigned int local_timer_irqs;
  4. } ____cacheline_aligned irq_cpustat_t;

所以raise_softirq_irqoff(TASKLET_SOFTIRQ)展开来就是:
irq_stat[cpu].__softirq_pending |= (1UL << TASKLET_SOFTIRQ);

Linux内核在中断返回时会检查__softirq_pending。关于这点,实际上是tasklet中的function啥时候会被调用的问题。起初我一度以为大约是在ret_from_interrupt(2.4的内核有这样的汇编函数)这样的汇编代码中来检查,然后我在ARM架构的arch/arm/kernel/entry-armv.S和entry-common.S中查了N久,也没有发现任何代码去检查这个__softirq_pending中的TASKLET_SOFTIRQ所在的位。后来终于在asm_do_IRQ函数中找到了...看来tasklet的调度时机比我原先预想的要提前了一点。完整代码如下:

  1. void irq_exit(void)
  2. {
  3.         account_system_vtime(current);
  4.         trace_hardirq_exit();
  5.         sub_preempt_count(IRQ_EXIT_OFFSET);
  6.         if (!in_interrupt() && local_softirq_pending())
  7.                 invoke_softirq();

  8. #ifdef CONFIG_NO_HZ
  9.        
  10.         rcu_irq_exit();
  11.         if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
  12.                 tick_nohz_stop_sched_tick(0);
  13. #endif
  14.         preempt_enable_no_resched();
  15. }

其中的

  1. if (!in_interrupt() && local_softirq_pending())
  2.                 invoke_softirq();

便是用来“在系统方便时”调度执行tasklet.还有一个问题,tasklet调用时,中断已经打开,在那里?

0

阅读 收藏 喜欢 打印举报/Report
前一篇:top查看iowait
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有

driver安装的中断处理函数-->退出点 通常说Linux下的中断处理分为top和bottom两部分... (来自 @头条博客)"}); -->