本文基于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.\n");
+}
+
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();
raise_softirq(LANCE_TEST); return ;
} 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: / # 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 = {};
static struct workqueue_struct *queue; void work_queue_cb(void)
{
printk(KERN_EMERG "This is work_queue test.\n");
printk("Cur in interrupt context? %s.\n", (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();
queue_work(queue, &lance_work); return ;
} 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 = {};
static struct timer_list timer = {}; static void task_func(unsigned long data)
{
printk("This is tasklet test.\n");
printk("Cur in interrupt context? %s.\n", (in_interrupt() ? "Yes" : "No"));
//msleep(1000); ///< 会导致崩溃
} void timer_handler(unsigned long data)
{
tasklet_schedule(&tasklet);
mod_timer(&timer, jiffies+msecs_to_jiffies());
} static int tasklet_test_init(void)
{
tasklet_init(&tasklet, task_func, );
init_timer(&timer); timer.function = timer_handler;
timer.expires = jiffies + HZ;
add_timer(&timer); return ;
} 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,但使用难度较大一些,要注意程序的可重入性。

最新文章

  1. 前端应当了解的Web缓存知识
  2. iOS开发——UI基础-UIImage,UIImageView的使用
  3. Linux每天定时重启Tomcat服务
  4. Android:控件布局(表格布局)TableLayout
  5. URL学习笔记
  6. VMware下打开Chrome OS遇到没有网络连接可用
  7. jsp中如何获得url路径和绝对路径
  8. XCode 6 出现 no identity found: Command /usr/bin/codesign failed with exit code 1 解决方法汇总
  9. Android Studio JNI开发入门教程
  10. unity静态批处理原理理解
  11. javascript ES3小测试
  12. 逻辑卷管理lvm
  13. 洛谷 [P2024] 食物链
  14. Python实现:汉诺塔问题
  15. TCP/IP协议示意图
  16. dmi-ipmi
  17. 03 字符串常用操作方法及For 循环
  18. igmp组播测试环境搭建
  19. CSS3实现图片循环旋转
  20. jquery Ajax请求中显示Loading...

热门文章

  1. Datagrip 快捷键和常用插件持续更新一集一些使用技巧
  2. java容器(一) Collection类框架图解
  3. jdk和dubbo的SPI机制
  4. 自己动手开发手机APP控制西门子200smart 教程(原创干货)
  5. CSS的常用单位介绍
  6. Linq扩展方法获取单个元素
  7. 使用stringstream打破字符与其他类型之间的隔阂
  8. latex使用总结
  9. Codeforces_818
  10. 使用github--stanfordnlp--glove训练自己的数据词向量