zoukankan      html  css  js  c++  java
  • 关于对 softirq、work_queue、tasklet 学习后的一点总结

    本文基于linux版本:4.14.111

    简单的总结下 softirq、work_queue、tasklet 三种中断下半部的工作原理及区别,并附上三种形式的简单实例。

    一、运行原理
    ① softirq:

    void __do_softirq(void)
    {
        int max_restart = MAX_SOFTIRQ_RESTART;         ///< 10
        struct softirq_action *h;
        ...
        pending = local_softirq_pending();          ///< 获取到当前的 pending 结果,也就是各个 softirq 的位或结果
        __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);  ///< 标识进入了 softirq,并禁止 preempt
    while ((softirq_bit = ffs(pending))) { ... /* 取出相应的 action 并执行 */ h->action(h); .... } if (pending) { /* 如果 softirq 整体运行完一遍后仍有 softirq 请求,那么将再次 restart 运行,最多运行 10 遍 */ if (time_before(jiffies, end) && !need_resched() && --max_restart) goto restart; /* 超过了 10 遍之后,不再 restart 运行,将请求交给处理 softirq 的内核线程,之后开启调度不占用过多时间片 */ wakeup_softirqd(); } ... }

    关于处理 softirq 请求的内核线程:

    static void wakeup_softirqd(void)
    {
        struct task_struct *tsk = __this_cpu_read(ksoftirqd);
    
        if (tsk && tsk->state != TASK_RUNNING)
            wake_up_process(tsk);       ///< 唤醒 ksoftirqd
    }
    
    static void run_ksoftirqd(unsigned int cpu)
    {
        local_irq_disable();
        if (local_softirq_pending()) {
            __do_softirq();             ///< 执行 __do_softirq
            local_irq_enable();
            cond_resched_rcu_qs();
            return;
        }
        local_irq_enable();
    }

    可见,ksoftirqd 的处理方式也同样是通过调用 __do_softirq 来运行 softirq。
    softirq 的触发方式:
      1) 通过 __do_softirq 主动触发,通常在硬中断退出时,即 irq_exit,也会通过调用 invoke_softirq() -> __do_softirq 来触发软中断来执行下半部的 ISR(Interrupt Service Routines);
      2) 通过 ksoftirqd 被动触发;

    ② work_queue:依赖的就是内核线程,会在之后的文章中详细说明一下,并会在此处附上链接(以 SPI Flash 驱动中的 kthread 相关操作为例)。

    ③ tasklet:是 softirq 中的一个 action,可理解为是一个特殊的 softirq,并在 softirq_init 时就得到初始化,其回调函数为 tasklet_action,这里不在阐述,同样运行在中断上下文。

    二、工作方式的区别
      softirq 与 tasklet 运行在中断上下文,运行期间不可出现 sleep 休眠、阻塞等操作,work_queue 运行在进程上下文,可以进行休眠、阻塞、发生调度等。

    三、测试实例及运行结果

    ① softirq:

    1) 第一部分是对内核的修改,因为 softirq 只能静态创建,修改文件为 /kernel/softirq.c 补丁文件如下:

     const char * const softirq_to_name[NR_SOFTIRQS] = {
         "HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "IRQ_POLL",
    -    "TASKLET", "SCHED", "HRTIMER", "RCU"
    +    "TASKLET", "SCHED", "HRTIMER", "RCU", "LANCE_TEST"
     };
     
     /*
    @@ -441,6 +441,7 @@ void raise_softirq(unsigned int nr)
         raise_softirq_irqoff(nr);
         local_irq_restore(flags);
     }
    +EXPORT_SYMBOL(raise_softirq);
     
    +static __latent_entropy void softirq_test_action(struct softirq_action *a)
    +{
    +    printk("This is softirq test.
    ");
    +}
    +
     void __init softirq_init(void)
     {
         int cpu;
    @@ -652,6 +658,8 @@ void __init softirq_init(void)
     
         open_softirq(TASKLET_SOFTIRQ, tasklet_action);
         open_softirq(HI_SOFTIRQ, tasklet_hi_action);
    +
    +    open_softirq(LANCE_TEST, softirq_test_action);
     }

     2) 编写测试模块:

    #include <linux/export.h>
    #include <linux/kernel_stat.h>
    #include <linux/interrupt.h>
    #include <linux/init.h>
    #include <linux/mm.h>
    #include <linux/notifier.h>
    #include <linux/percpu.h>
    #include <linux/cpu.h>
    #include <linux/freezer.h>
    #include <linux/kthread.h>
    #include <linux/rcupdate.h>
    #include <linux/ftrace.h>
    #include <linux/smp.h>
    #include <linux/smpboot.h>
    #include <linux/tick.h>
    #include <linux/irq.h>
    
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/delay.h>
    
    static int softirq_test_init(void)
    {
        raise_softirq(LANCE_TEST);
        msleep(1000);
        raise_softirq(LANCE_TEST);
        
        return 0;    
    }
    
    static void softirq_test_exit(void)
    {
    }
    
    module_init(softirq_test_init);
    module_exit(softirq_test_exit);
    MODULE_LICENSE("GPL");

     3) 安装模块后测试结果:

    cat proc/softirqs 
                        CPU0       CPU1       CPU2       CPU3       
      LANCE_TEST:          0          0          0          0
      
    / # insmod softirq.ko 
    [ 9544.236966] This is softirq test.
    [ 9545.252842] This is softirq test.

     ② work_queue:

    1) 编写测试模块:

    #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/fs.h>
    #include <linux/module.h>
    #include <linux/time.h>
    #include <linux/delay.h>
    
    static struct work_struct lance_work = {0};
    static struct workqueue_struct *queue;
    
    void work_queue_cb(void)
    {
        printk(KERN_EMERG "This is work_queue test.
    ");
        printk("Cur in interrupt context? %s.
    ", (in_interrupt() ? "Yes" : "No"));
    }
    
    static int work_queue_init(void)
    {
        queue = create_workqueue("work_queue_lance"); 
        INIT_WORK(&lance_work, (typeof(lance_work.func))work_queue_cb);
        queue_work(queue, &lance_work); 
        
        msleep(1000);
        queue_work(queue, &lance_work); 
        
        return 0;    
    }
    
    static void work_queue_exit(void)
    {
        destroy_workqueue(queue); 
    }
    
    module_init(work_queue_init);
    module_exit(work_queue_exit);
    MODULE_LICENSE("GPL");

     2)  安装模块后测试结果:

    / # insmod work_queue.ko 
    [12633.889983] This is work_queue test.
    [12633.893577] Cur in interrupt context? No.
    [12634.917039] This is work_queue test.
    / # [12634.920623] Cur in interrupt context? No.

     ③ tasklet:

    1) 编写测试模块:

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/timer.h>
    #include <linux/delay.h>
    #include <linux/interrupt.h>
    
    static struct tasklet_struct tasklet = {0};
    static struct timer_list timer = {0};
    
    static void task_func(unsigned long data)
    {
        printk("This is tasklet test.
    ");
        printk("Cur in interrupt context? %s.
    ", (in_interrupt() ? "Yes" : "No"));
        //msleep(1000);        ///< 会导致崩溃    
    }
    
    void timer_handler(unsigned long data)
    {
        tasklet_schedule(&tasklet);
        mod_timer(&timer, jiffies+msecs_to_jiffies(2000));
    }
    
    static int tasklet_test_init(void)
    {
        tasklet_init(&tasklet, task_func, 0);
        init_timer(&timer);
        
        timer.function = timer_handler;
        timer.expires = jiffies + HZ;
        add_timer(&timer);
        
        return 0;    
    }
    
    static void tasklet_test_exit(void)
    {
        del_timer(&timer);
        tasklet_disable(&tasklet);
    }
    
    module_init(tasklet_test_init);
    module_exit(tasklet_test_exit);
    MODULE_LICENSE("GPL");

     2)  安装模块后测试结果:

    / # insmod tasklet.ko 
    / # [12477.508765] This is tasklet test.
    [12477.512089] Cur in interrupt context? Yes.
    [12479.524783] This is tasklet test.
    [12479.528108] Cur in interrupt context? Yes.

     

    四、文末总结下在编写一个驱动时, 如果选用这三种工作方式

    从根本上来说,如果有休眠阻塞的需要,work_queue 是唯一的选择;

    否则最好用 tasklet,如果必须专注于性能的提高,那么就要考虑 softirq,但使用难度较大一些,要注意程序的可重入性。

  • 相关阅读:
    软件工程实践总结
    用户使用调查报告
    Beta 冲刺 随笔合集
    Beta 冲刺 七
    Beta 冲刺 六
    Beta 冲刺 五
    Beta 冲刺 四
    Beta 冲刺 三
    Beta 冲刺 二
    Beta 冲刺 一
  • 原文地址:https://www.cnblogs.com/GyForever1004/p/12355101.html
Copyright © 2011-2022 走看看