转自:http://blog.csdn.net/droidphone/article/details/7518428

软件中断(softIRQ)是内核提供的一种延迟执行机制,它完全由软件触发,虽然说是延迟机制,实际上,在大多数情况下,它与普通进程相比,能得到更快的响应时间。软中断也是其他一些内核机制的基础,比如tasklet,高分辨率timer等。

/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/

1.  软件中断的数据结构

 

1.1  struct softirq_action

 
        内核用softirq_action结构管理软件中断的注册和激活等操作,它的定义如下:
  1. struct softirq_action
  2. {
  3. void    (*action)(struct softirq_action *);
  4. };

非常简单,只有一个用于回调的函数指针。软件中断的资源是有限的,内核目前只实现了10种类型的软件中断,它们是:

  1. enum
  2. {
  3. HI_SOFTIRQ=0,
  4. TIMER_SOFTIRQ,
  5. NET_TX_SOFTIRQ,
  6. NET_RX_SOFTIRQ,
  7. BLOCK_SOFTIRQ,
  8. BLOCK_IOPOLL_SOFTIRQ,
  9. TASKLET_SOFTIRQ,
  10. SCHED_SOFTIRQ,
  11. HRTIMER_SOFTIRQ,
  12. RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */
  13. NR_SOFTIRQS
  14. };

内核的开发者们不建议我们擅自增加软件中断的数量,如果需要新的软件中断,尽可能把它们实现为基于软件中断的tasklet形式。与上面的枚举值相对应,内核定义了一个softirq_action的结构数组,每种软中断对应数组中的一项:

  1. static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

1.2  irq_cpustat_t

 
        多个软中断可以同时在多个cpu运行,就算是同一种软中断,也有可能同时在多个cpu上运行。内核为每个cpu都管理着一个待决软中断变量(pending),它就是irq_cpustat_t:
  1. typedef struct {
  2. unsigned int __softirq_pending;
  3. } ____cacheline_aligned irq_cpustat_t;
  1. irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;

__softirq_pending字段中的每一个bit,对应着某一个软中断,某个bit被置位,说明有相应的软中断等待处理。

1.3  软中断的守护进程ksoftirqd

 
        在cpu的热插拔阶段,内核为每个cpu创建了一个用于执行软件中断的守护进程ksoftirqd,同时定义了一个per_cpu变量用于保存每个守护进程的task_struct结构指针:
  1. DEFINE_PER_CPU(struct task_struct *, ksoftirqd);

大多数情况下,软中断都会在irq_exit阶段被执行,在irq_exit阶段没有处理完的软中断才有可能会在守护进程中执行。

2.  触发软中断

 
        要触发一个软中断,只要调用api:raise_softirq即可,它的实现很简单,先是关闭本地cpu中断,然后调用:raise_softirq_irqoff
  1. void raise_softirq(unsigned int nr)
  2. {
  3. unsigned long flags;
  4. local_irq_save(flags);
  5. raise_softirq_irqoff(nr);
  6. local_irq_restore(flags);
  7. }

再看看raise_softirq_irqoff:

  1. inline void raise_softirq_irqoff(unsigned int nr)
  2. {
  3. __raise_softirq_irqoff(nr);
  4. ......
  5. if (!in_interrupt())
  6. wakeup_softirqd();
  7. }

先是通过__raise_softirq_irqoff设置cpu的软中断pending标志位(irq_stat[NR_CPUS] ),然后通过in_interrupt判断现在是否在中断上下文中,或者软中断是否被禁止,如果都不成立,则唤醒软中断的守护进程,在守护进程中执行软中断的回调函数。否则什么也不做,软中断将会在中断的退出阶段被执行。

3.  软中断的执行

 
        基于上面所说,软中断的执行既可以守护进程中执行,也可以在中断的退出阶段执行。实际上,软中断更多的是在中断的退出阶段执行(irq_exit),以便达到更快的响应,加入守护进程机制,只是担心一旦有大量的软中断等待执行,会使得内核过长地留在中断上下文中。

3.1  在irq_exit中执行

        看看irq_exit的部分:
  1. void irq_exit(void)
  2. {
  3. ......
  4. sub_preempt_count(IRQ_EXIT_OFFSET);
  5. if (!in_interrupt() && local_softirq_pending())
  6. invoke_softirq();
  7. ......
  8. }

如果中断发生嵌套,in_interrupt()保证了只有在最外层的中断的irq_exit阶段,invoke_interrupt才会被调用,当然,local_softirq_pending也会实现判断当前cpu有无待决的软中断。代码最终会进入__do_softirq中,内核会保证调用__do_softirq时,本地cpu的中断处于关闭状态,进入__do_softirq:

  1. asmlinkage void __do_softirq(void)
  2. {
  3. ......
  4. pending = local_softirq_pending();
  5. __local_bh_disable((unsigned long)__builtin_return_address(0),
  6. SOFTIRQ_OFFSET);
  7. restart:
  8. /* Reset the pending bitmask before enabling irqs */
  9. set_softirq_pending(0);
  10. local_irq_enable();
  11. h = softirq_vec;
  12. do {
  13. if (pending & 1) {
  14. ......
  15. trace_softirq_entry(vec_nr);
  16. h->action(h);
  17. trace_softirq_exit(vec_nr);
  18. ......
  19. }
  20. h++;
  21. pending >>= 1;
  22. } while (pending);
  23. local_irq_disable();
  24. pending = local_softirq_pending();
  25. if (pending && --max_restart)
  26. goto restart;
  27. if (pending)
  28. wakeup_softirqd();
  29. lockdep_softirq_exit();
  30. __local_bh_enable(SOFTIRQ_OFFSET);
  31. }
  • 首先取出pending的状态;
  • 禁止软中断,主要是为了防止和软中断守护进程发生竞争;
  • 清除所有的软中断待决标志;
  • 打开本地cpu中断;
  • 循环执行待决软中断的回调函数;
  • 如果循环完毕,发现新的软中断被触发,则重新启动循环,直到以下条件满足,才退出:
    • 没有新的软中断等待执行;
    • 循环已经达到最大的循环次数MAX_SOFTIRQ_RESTART,目前的设定值时10次;
  • 如果经过MAX_SOFTIRQ_RESTART次循环后还未处理完,则激活守护进程,处理剩下的软中断;
  • 推出前恢复软中断;

3.2  在ksoftirqd进程中执行

        从前面几节的讨论我们可以看出,软中断也可能由ksoftirqd守护进程执行,这要发生在以下两种情况下:
  • 在irq_exit中执行软中断,但是在经过MAX_SOFTIRQ_RESTART次循环后,软中断还未处理完,这种情况虽然极少发生,但毕竟有可能;
  • 内核的其它代码主动调用raise_softirq,而这时正好不是在中断上下文中,守护进程将被唤醒;

守护进程最终也会调用__do_softirq执行软中断的回调,具体的代码位于run_ksoftirqd函数中,内核会关闭抢占的情况下执行__do_softirq,具体的过程这里不做讨论。

4.  tasklet

 
       因为内核已经定义好了10种软中断类型,并且不建议我们自行添加额外的软中断,所以对软中断的实现方式,我们主要是做一个简单的了解,对于驱动程序的开发者来说,无需实现自己的软中断。但是,对于某些情况下,我们不希望一些操作直接在中断的handler中执行,但是又希望在稍后的时间里得到快速地处理,这就需要使用tasklet机制。 tasklet是建立在软中断上的一种延迟执行机制,它的实现基于TASKLET_SOFTIRQ和HI_SOFTIRQ这两个软中断类型。

4.1  tasklet_struct

 
在软中断的初始化函数softirq_init的最后,内核注册了TASKLET_SOFTIRQ和HI_SOFTIRQ这两个软中断:
  1. void __init softirq_init(void)
  2. {
  3. ......
  4. open_softirq(TASKLET_SOFTIRQ, tasklet_action);
  5. open_softirq(HI_SOFTIRQ, tasklet_hi_action);
  6. }

内核用一个tasklet_struct来表示一个tasklet,它的定义如下:

  1. struct tasklet_struct
  2. {
  3. struct tasklet_struct *next;
  4. unsigned long state;
  5. atomic_t count;
  6. void (*func)(unsigned long);
  7. unsigned long data;
  8. };

next用于把同一个cpu的tasklet链接成一个链表,state用于表示该tasklet的当前状态,目前只是用了最低的两个bit,分别用于表示已经准备被调度执行和已经在另一个cpu上执行:

  1. enum
  2. {
  3. TASKLET_STATE_SCHED,    /* Tasklet is scheduled for execution */
  4. TASKLET_STATE_RUN   /* Tasklet is running (SMP only) */
  5. };

原子变量count用于tasklet对tasklet_disable和tasklet_enable的计数,count为0时表示允许tasklet执行,否则不允许执行,每次tasklet_disable时,该值加1,tasklet_enable时该值减1。func是tasklet被执行时的回调函数指针,data则用作回调函数func的参数。

4.2  初始化一个tasklet

 
有两种办法初始化一个tasklet,第一种是静态初始化,使用以下两个宏,这两个宏定义一个tasklet_struct结构,并用相应的参数对结构中的字段进行初始化:
  • DECLARE_TASKLET(name, func, data);定义名字为name的tasklet,默认为enable状态,也就是count字段等于0。
  • DECLARE_TASKLET_DISABLED(name, func, data);定义名字为name的tasklet,默认为enable状态,也就是count字段等于1。

第二个是动态初始化方法:先定义一个tasklet_struct,然后用tasklet_init函数进行初始化,该方法默认tasklet处于enable状态:

  1. struct tasklet_struct tasklet_xxx;
  2. ......
  3. tasklet_init(&tasklet_xxx, func, data);

4.3  tasklet的使用方法

 
使能和禁止tasklet,使用以下函数:
  • tasklet_disable()  通过给count字段加1来禁止一个tasklet,如果tasklet正在运行中,则等待运行完毕才返回(通过TASKLET_STATE_RUN标志)。
  • tasklet_disable_nosync()  tasklet_disable的异步版本,它不会等待tasklet运行完毕。
  • tasklet_enable()  使能tasklet,只是简单地给count字段减1。

调度tasklet的执行,使用以下函数:

  • tasklet_schedule(struct tasklet_struct *t)  如果TASKLET_STATE_SCHED标志为0,则置位TASKLET_STATE_SCHED,然后把tasklet挂到该cpu等待执行的tasklet链表上,接着发出TASKLET_SOFTIRQ软件中断请求。
  • tasklet_hi_schedule(struct tasklet_struct *t)  效果同上,区别是它发出的是HI_SOFTIRQ软件中断请求。

销毁tasklet,使用以下函数:

  • tasklet_kill(struct tasklet_struct *t)  如果tasklet处于TASKLET_STATE_SCHED状态,或者tasklet正在执行,则会等待tasklet执行完毕,然后清除TASKLET_STATE_SCHED状态。

4.4  tasklet的内部执行机制

 
内核为每个cpu用定义了一个tasklet_head结构,用于管理每个cpu上的tasklet的调度和执行:
  1. struct tasklet_head
  2. {
  3. struct tasklet_struct *head;
  4. struct tasklet_struct **tail;
  5. };
  6. static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
  7. static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

回到4.1节,我们知道,tasklet是利用TASKLET_SOFTIRQ和HI_SOFTIRQ这两个软中断来实现的,两个软中断只是有优先级的差别,所以我们只讨论TASKLET_SOFTIRQ的实现,TASKLET_SOFTIRQ的中断回调函数是tasklet_action,我们看看它的代码:

  1. static void tasklet_action(struct softirq_action *a)
  2. {
  3. struct tasklet_struct *list;
  4. local_irq_disable();
  5. list = __this_cpu_read(tasklet_vec.head);
  6. __this_cpu_write(tasklet_vec.head, NULL);
  7. __this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head);
  8. local_irq_enable();
  9. while (list) {
  10. struct tasklet_struct *t = list;
  11. list = list->next;
  12. if (tasklet_trylock(t)) {
  13. if (!atomic_read(&t->count)) {
  14. if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
  15. BUG();
  16. t->func(t->data);
  17. tasklet_unlock(t);
  18. continue;
  19. }
  20. tasklet_unlock(t);
  21. }
  22. local_irq_disable();
  23. t->next = NULL;
  24. *__this_cpu_read(tasklet_vec.tail) = t;
  25. __this_cpu_write(tasklet_vec.tail, &(t->next));
  26. __raise_softirq_irqoff(TASKLET_SOFTIRQ);
  27. local_irq_enable();
  28. }
  29. }

解析如下:

  • 关闭本地中断的前提下,移出当前cpu的待处理tasklet链表到一个临时链表后,清除当前cpu的tasklet链表,之所以这样处理,是为了处理当前tasklet链表的时候,允许新的tasklet被调度进待处理链表中。
  • 遍历临时链表,用tasklet_trylock判断当前tasklet是否已经在其他cpu上运行,而且tasklet没有被禁止:
    • 如果没有运行,也没有禁止,则清除TASKLET_STATE_SCHED状态位,执行tasklet的回调函数。
    • 如果已经在运行,或者被禁止,则把该tasklet重新添加会当前cpu的待处理tasklet链表上,然后触发TASKLET_SOFTIRQ软中断,等待下一次软中断时再次执行。

分析到这了我有个疑问,看了上面的代码,如果一个tasklet被tasklet_schedule后,在没有被执行前被tasklet_disable了,岂不是会无穷无尽地引发TASKLET_SOFTIRQ软中断?

通过以上的分析,我们需要注意的是,tasklet有以下几个特征:
  • 同一个tasklet只能同时在一个cpu上执行,但不同的tasklet可以同时在不同的cpu上执行;
  • 一旦tasklet_schedule被调用,内核会保证tasklet一定会在某个cpu上执行一次;
  • 如果tasklet_schedule被调用时,tasklet不是出于正在执行状态,则它只会执行一次;
  • 如果tasklet_schedule被调用时,tasklet已经正在执行,则它会在稍后被调度再次被执行;
  • 两个tasklet之间如果有资源冲突,应该要用自旋锁进行同步保护;

Linux中断(interrupt)子系统之五:软件中断(softIRQ)的更多相关文章

  1. Linux中断(interrupt)子系统

    Linux中断(interrupt)子系统之一:中断系统基本原理 Linux中断(interrupt)子系统之二:arch相关的硬件封装层 Linux中断(interrupt)子系统之三:中断流控处理 ...

  2. Linux中断(interrupt)子系统之一:中断系统基本原理

    这个中断系列文章主要针对移动设备中的Linux进行讨论,文中的例子基本都是基于ARM这一体系架构,其他架构的原理其实也差不多,区别只是其中的硬件抽象层.内核版本基于3.3.虽然内核的版本不断地提升,不 ...

  3. 【原创】Linux中断子系统(三)-softirq和tasklet

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  4. Linux中断(interrupt)子系统之四:驱动程序接口层 & 中断通用逻辑层【转】

    转自:http://blog.csdn.net/droidphone/article/details/7497787 在本系列文章的第一篇:Linux中断(interrupt)子系统之一:中断系统基本 ...

  5. Linux中断(interrupt)子系统之一:中断系统基本原理【转】

    转自:http://blog.csdn.net/droidphone/article/details/7445825 这个中断系列文章主要针对移动设备中的Linux进行讨论,文中的例子基本都是基于AR ...

  6. Linux中断(interrupt)子系统之二:arch相关的硬件封装层【转】

    转自:http://blog.csdn.net/droidphone/article/details/7467436 Linux的通用中断子系统的一个设计原则就是把底层的硬件实现尽可能地隐藏起来,使得 ...

  7. Linux时间子系统之五:低分辨率定时器的原理和实现

    专题文档汇总目录 Notes:低精度timer在内核中的数据结构以及API接口:低精度timer精巧高效的分组,使用cascade进行定时器移位,组内Timer FIFO:低精度Timer的初始化流程 ...

  8. Intel 80x86 Linux Kernel Interrupt(中断)、Interrupt Priority、Interrupt nesting、Prohibit Things Whthin CPU In The Interrupt Off State

    目录 . 引言 . Linux 中断的概念 . 中断处理流程 . Linux 中断相关的源代码分析 . Linux 硬件中断 . Linux 软中断 . 中断优先级 . CPU在关中断状态下编程要注意 ...

  9. linux 中断softirq tasklet

    硬中断为什么不能休眠--- 中断线程以及软中断解决了什么问题---- 软中断cb函数是否允许相应本地中断,什么时候开启中断关闭中断---- 什么是软中断上下文------- 什么是tasklet 和软 ...

  10. Linux中断 - softirq

    一.前言 对于中断处理而言,linux将其分成了两个部分,一个叫做中断handler(top half),是全程关闭中断的,另外一部分是deferable task(bottom half),属于不那 ...

随机推荐

  1. php环境搭建及入门

    在php文件里,写入header('content-type:text/html;charset = uft-8'); <?php header('content-type:text/html; ...

  2. golang yaml配置文件解析

    yaml文件语法 此模块内容转自:http://www.ruanyifeng.com/blog/2016/07/yaml.html 大小写敏感 使用缩进表示层级关系 缩进时不允许使用Tab键,只允许使 ...

  3. layout图形化界面看不到内容 Failed to find the style corresponding to the id

    1.问题 在创建新的工程的时候,选择目标SDK为api21,编译SDK为api23.创建出来的layout文件图形化界面中看不到,并且报错: Failed to find the style corr ...

  4. 关于PSP(个人软件过程)

    在第一堂课时,杨老师就提到了PSP(个人软件过程),但是我从2016年3月10日才开始进行粗略的PSP时间管理统计,这是长迭代,用老师的话“差评”.然后在2016年3月11日下午的软件项目管理上,老师 ...

  5. 微信小游戏 4M升8M分包加载

    一.微信分包加载 微信分包加载教程 嘛,因为原来的4M太小了,满足不了小游戏内容的需求,现在提升到了8M.这8M可以分包加载,而不需要一次性加载8M. 如果是老版本,则分包加载不起作用,会一次加载8M ...

  6. blog 社会化评论插件 多说for china, disqus for global range

    1.disqus https://disqus.com/ https://publishers.disqus.com/engage https://disqus.com/admin/create/ h ...

  7. 神奇的Redis延迟

    最近在做某业务Redis的缩容工作,涉及到数据迁移,而Redis的数据迁移看起来蛮简单的,一对一的数据迁移只需要在slave行配置masterauth 和slaveof 两个参数即可,当然迁移过程中涉 ...

  8. MT【184】$\epsilon$助力必要性

    已知满足不等式$|x^2-4x+a|+|x-3|\le5$的最大值为$3$,求实数$a$的值,并解该不等式. 证明:1)当$x=3$时,$|a-3|\le5$,得$a\in[-2,8]$2)$\for ...

  9. aws上部署zabbix3.4

    三台机器 10.0.0.149 AmazonLinux2.0 zabbix-server zabbix-agent 10.0.1.61 CentOS6.9 zabbix-agent 10.0.1.11 ...

  10. java旋转图片

    /** * 旋转角度 * @param src 源图片 * @param angel 角度 * @return 目标图片 */ public static BufferedImage rotate(I ...