1 软中断概述

软中断是实现中断下半部的一种手段,与2.5以前版本的下半段机制不同。软中断可以同时运行在不同的CPU上。

1.1 软中断的表示

内核中用结构体softirq_action表示一个软中断。软中断是一组静态定义的接口,有32个。但是内核(2.6.34)中只实现了10个。可用的软中断的个数用NR_SOFTIRQ表示,NR_SOFTIRQ=10,软中断的类型用一个枚举体表示。这里需要注意的是,32个软中断体现在位掩码是unsigned int 类型。

  1. static struct softirq_action softirq_vec[NR_SOFTIRQS] ;
  2. enum
  3. {
  4. HI_SOFTIRQ=0,
  5. TIMER_SOFTIRQ,
  6. NET_TX_SOFTIRQ,
  7. NET_RX_SOFTIRQ,
  8. BLOCK_SOFTIRQ,
  9. BLOCK_IOPOLL_SOFTIRQ,
  10. TASKLET_SOFTIRQ,
  11. SCHED_SOFTIRQ,
  12. HRTIMER_SOFTIRQ,
  13. RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
  14. NR_SOFTIRQS
  15. };
  16. struct softirq_action
  17. {
  18. void (*action)(struct softirq_action *);
  19. };

2 软中断相关的数据结构

2.1 thread_info的preempt_count字段

preempt_count 是一个32位的int型,共分为5个字段



宏in_interrupt检测软中断计数器 硬中断计数器 和 NMI掩码,只要这三个字段任意一个字段不为0,就表示进程处于中断上下文。

  1. #define in_irq() (hardirq_count())
  2. #define in_softirq() (softirq_count())
  3. #define in_interrupt() (irq_count())

2.2 pending位掩码

每个CPU上都有一个irq_stat结构,irq_stat中的__softirq_pending是一个32位的掩码,为1表示该软中断已经激活,正等待处理。为0表示软中断被禁止。在do_irq中被使用。

内核使用local_softirq_pending得到当前CPU上的位掩码

  1. #define local_softirq_pending() percpu_read(irq_stat.__softirq_pending)
  2. #define set_softirq_pending(x) percpu_write(irq_stat.__softirq_pending, (x))
  3. #define or_softirq_pending(x) percpu_or(irq_stat.__softirq_pending, (x))
  4. irq_cpustat_t irq_stat[NR_CPUS]
  5. typedef struct {
  6. ...
  7. unsigned int __softirq_pending;
  8. ...
  9. }irq_cpustat_t;

2.3 软中断栈

进程的内核栈的大小根据编译时选项不同,可以是4K或者8K。如果是8K堆栈,中断,异常和软中断(softirq) 共享这个堆栈。如果选择4K堆栈,则内核堆栈 硬中断堆栈 软中断堆栈各自使用一个4K空间。关于软中断堆栈,后面在软中断处理时再详细说明。

  1. #ifdef CONFIG_4KSTACKS
  2. static DEFINE_PER_CPU_PAGE_ALIGNED(union irq_ctx, softirq_stack);
  3. union irq_ctx {
  4. struct thread_info tinfo;
  5. u32 stack[THREAD_SIZE/sizeof(u32)];
  6. } __attribute__((aligned(PAGE_SIZE)));

3 软中断的初始化

内核使用open_softirq初始化一个软中断,nr是代表软中断类型的常量,action指向一个软中断处理函数

  1. void open_softirq(int nr, void (*action)(struct softirq_action *))
  2. {
  3. softirq_vec[nr].action = action;
  4. }

4 软中断的触发(raise softirq)

触发就是将位掩码pending的相应位 置1的过程。内核使用raise_softirq完成触发软中断,nr是要触发的软中断类型。值的注意的是,中断的触发 发生关闭硬中断的情况下。

触发软中断的过程中,如果该进程未处于中断上下文,说明当前进程处于进程上下文中,那么我们直接调用wakeup_softirqd调度ksoftirqd即可。

反之,如果当前处于中断上下文中或软中断被禁止使用,那么就不必调度内核线程,在中断处理后期irq_exit中,会调用invoke_softirq()处理软中断。

实际的工作是交给or_softirq_pending(1UL << (nr)); 完成的,该函数通过位操作将指定为和pending相加。

  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. }
  8. inline void raise_softirq_irqoff(unsigned int nr)
  9. {
  10. __raise_softirq_irqoff(nr);
  11. /*
  12. * If we're in an interrupt or softirq, we're done
  13. * (this also catches softirq-disabled code). We will
  14. * actually run the softirq once we return from
  15. * the irq or softirq.
  16. *
  17. * Otherwise we wake up ksoftirqd to make sure we
  18. * schedule the softirq soon.
  19. * 如果不在中断上下文中
  20. */
  21. if (!in_interrupt())
  22. wakeup_softirqd();
  23. }
  24. #define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); }

5. 软中断的处理

5.1 处理软中断的时机

1 在do_irq的末期(irq_exit)

如果当前进程没有处于中断上下文中并且本地CPU上还有没有处理的软中断,那么就调用invoke_softirq()处理软中断。

  1. #ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
  2. # define invoke_softirq() __do_softirq()
  3. #else
  4. # define invoke_softirq() do_softirq()
  5. #endif
  6. void irq_exit(void)
  7. {
  8. account_system_vtime(current);
  9. trace_hardirq_exit();
  10. sub_preempt_count(IRQ_EXIT_OFFSET);
  11. if (!in_interrupt() && local_softirq_pending())
  12. invoke_softirq();
  13. rcu_irq_exit();
  14. #ifdef CONFIG_NO_HZ
  15. /* Make sure that timer wheel updates are propagated */
  16. if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
  17. tick_nohz_stop_sched_tick(0);
  18. #endif
  19. preempt_enable_no_resched();
  20. }
  21. #ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
  22. # define invoke_softirq() __do_softirq()
  23. #else
  24. # define invoke_softirq() do_softirq()
  25. #endif

程序5-1 irq_exit

2 当软中断被重复触发超过10次时,内核会调用wakeup_softirqd()唤醒内核线程ksoftirqd去处理软中断。

5.2 软中断的处理

1 do_softirq

根据内核堆栈大小,有两种do_softirq,一种是通用do_irq,另一种是架构相关的do_irq(arch/x86/kernel/irq_32.c)。

通用的do_irq的工作流程

1 判断当前是否处于硬中断上下文中或者软中断被禁用,如果是那么直接返回

2 保存Eflags 然后关闭本地硬件中断

3 获取本地CPU的位掩码pending 如果有待处理的软中断就调用__do_irq

4 从__do_irq返回恢复Eflags

  1. asmlinkage void do_softirq(void)
  2. {
  3. //用来保存位掩码的局部变量
  4. __u32 pending;
  5. //保存Eflags寄存器的局部变量
  6. unsigned long flags;
  7. //如果do_softirq在中断上下文中被调用 或 软中断被禁止使用 那么不处理软中断
  8. //直接返回
  9. if (in_interrupt())
  10. return;
  11. //将Eflags保存到flags中 然后关硬件中断
  12. local_irq_save(flags);
  13. //获取本地CPU上的位掩码
  14. pending = local_softirq_pending();
  15. if (pending)
  16. __do_softirq();
  17. //将flags
  18. local_irq_restore(flags);
  19. }

x86_32架构下使用4K软中断堆栈的do_softirq处理流程

1 类似于通用的do_softirq,如果在中断上下文中或者软中断被禁止使用就立即返回。然后关外部中断

2 如果本地CPU上存在待处理的软中断就开始对软中断堆栈的处理,关键是令isp指向软中断堆栈的栈底。然后在软中断栈上调用call_on_stack。call_on_stack是一段内联汇编,其主要目的是完成从内核栈到软中断栈的切换。先将esp保存到ebx中,使用call指令跳转到__do_softirq子例程,子例程返回时再恢复esp。

  1. asmlinkage void do_softirq(void)
  2. {
  3. unsigned long flags;
  4. struct thread_info *curctx;
  5. union irq_ctx *irqctx;
  6. u32 *isp;
  7. if (in_interrupt())
  8. return;
  9. local_irq_save(flags);
  10. if (local_softirq_pending()) {
  11. //curctx指向当前进程的thread_info结构
  12. curctx = current_thread_info();
  13. //irqctx包含一个软中断堆和thread_info结构
  14. irqctx = __get_cpu_var(softirq_ctx);
  15. //触发硬中断和软中断是同一个进程所以将threadinfo的task指针统一
  16. irqctx->tinfo.task = curctx->task;
  17. //从内核堆栈切换到软中断堆栈 需要保存内核堆栈的栈指针寄存器内容
  18. irqctx->tinfo.previous_esp = current_stack_pointer;
  19. /* build the stack frame on the softirq stack */
  20. //isp指向软中断栈底
  21. isp = (u32 *) ((char *)irqctx + sizeof(*irqctx));
  22. //在软中断堆栈上调用__do_softirq
  23. call_on_stack(__do_softirq, isp);
  24. }
  25. local_irq_restore(flags);
  26. }
  27. static void call_on_stack(void *func, void *stack)
  28. {
  29. //call *%%edi 间接绝对近调用 偏移地址存储在edi寄存器中
  30. //指令执行时 先将eip入栈 然后将edi-->eip
  31. //先交换ebx and esp
  32. //然后调用__do_softirq
  33. //然后将ebx-->esp 恢复xchgl之前的esp
  34. //输出约束将ebx --> stack
  35. asm volatile("xchgl %%ebx,%%esp \n"
  36. "call *%%edi \n"
  37. "movl %%ebx,%%esp \n"
  38. : "=b" (stack)
  39. : "0" (stack),
  40. "D"(func)
  41. : "memory", "cc", "edx", "ecx", "eax");
  42. }

2 __do_softirq

软中断的处理实际上是由 __do_softirq完成,整体的思路是遍历pending,如果某一位不为空表示本地CPU上有待处理的软中断,然出调用软中断的处理函数。

开始处理软中断前,内核要调用__local_bh_disable(通过将preempt_count的软中断计数器加1)关闭下半部。如前所说,处理软中断的时机不止一个,内核要保证在本地CPU上软中断的处理是串行的。

另外在处理软中断的循环结束时,内核还要检测是否有重复触发的软中断。先调用local_softirq_pending()获取位掩码pending,然后根据pending继续处理软中断,不过这种重复处理不能超过10次(MAX_SOFTIRQ_RESTART),一旦超过10次,内核就会唤醒ksoftirqd

  1. #define MAX_SOFTIRQ_RESTART 10
  2. asmlinkage void __do_softirq(void)
  3. {
  4. // softirq_action表示一个软中断
  5. struct softirq_action *h;
  6. // 局部变量pending 保存待处理软中断位图
  7. __u32 pending;
  8. // 软中断的重启次数
  9. int max_restart = MAX_SOFTIRQ_RESTART;
  10. int cpu;
  11. //获取本地CPU上所有待处理的软中断
  12. pending = local_softirq_pending();
  13. account_system_vtime(current);
  14. //关闭下半部中断
  15. __local_bh_disable((unsigned long)__builtin_return_address(0));
  16. lockdep_softirq_enter();
  17. cpu = smp_processor_id();
  18. restart:
  19. /* Reset the pending bitmask before enabling irqs */
  20. //pending已经保存了所有带出软中断的状态 所以将pending bitmask clear
  21. set_softirq_pending(0);
  22. // 开中断
  23. local_irq_enable();
  24. //h指向第一类软中断
  25. h = softirq_vec;
  26. do {
  27. //先处理第一类软中断
  28. if (pending & 1) {
  29. int prev_count = preempt_count();
  30. kstat_incr_softirqs_this_cpu(h - softirq_vec);
  31. trace_softirq_entry(h, softirq_vec);
  32. //调用软中断处理程序
  33. h->action(h);
  34. trace_softirq_exit(h, softirq_vec);
  35. if (unlikely(prev_count != preempt_count())) {
  36. printk(KERN_ERR "huh, entered softirq %td %s %p"
  37. "with preempt_count %08x,"
  38. " exited with %08x?\n", h - softirq_vec,
  39. softirq_to_name[h - softirq_vec],
  40. h->action, prev_count, preempt_count());
  41. preempt_count() = prev_count;
  42. }
  43. rcu_bh_qs(cpu);
  44. }
  45. h++;
  46. pending >>= 1;
  47. } while (pending);
  48. //关闭本地CPU上的硬中断
  49. local_irq_disable();
  50. //获取位掩码
  51. pending = local_softirq_pending();
  52. if (pending && --max_restart)
  53. goto restart;
  54. if (pending)
  55. wakeup_softirqd();
  56. lockdep_softirq_exit();
  57. account_system_vtime(current);
  58. //将软中断计数器加1,激活软中断
  59. _local_bh_enable();
  60. }

6 ksoftirqd内核线程

6.1 ksoftirqd

在内核处理类似NET_RX_SOFTIRQ的软中断时,如果有大量等待处理的数据包。就会不断的调用

__raise_softirq_irqoff(NET_RX_SOFTIRQ)重复触发软中断NET_RX_SOFTIRQ。这样做会导致用户进程的” 饥饿问题 “ (长时间无法获得CPU)。

针对这种问题内核使用内核线程ksoftirqd去处理自行触发次数超过10次的软中断。

6.2 ksoftirqd的实现

  1. static int ksoftirqd(void * __bind_cpu)
  2. {
  3. //ksoftirqd的优先级最低(nice = 19)
  4. set_user_nice(current, 19);
  5. //将ksoftirqd设置为不可冻结
  6. current->flags |= PF_NOFREEZE;
  7. //设置ksoftirqd为可中断状态
  8. set_current_state(TASK_INTERRUPTIBLE);
  9. while (!kthread_should_stop()) {
  10. //如果没有待处理的软中断则 调度别的进程
  11. if (!local_softirq_pending())
  12. schedule();
  13. __set_current_state(TASK_RUNNING);
  14. while (local_softirq_pending()) {
  15. /* Preempt disable stops cpu going offline.
  16. If already offline, we'll be on wrong CPU:
  17. don't process */
  18. //关闭内核抢占
  19. preempt_disable();
  20. //处理软中断
  21. do_softirq();
  22. //开启内核抢占
  23. preempt_enable();
  24. //cond_resched()的目的是提高系统实时性, 主动放弃cpu供优先级更高的任务使用
  25. cond_resched();
  26. }
  27. set_current_state(TASK_INTERRUPTIBLE);
  28. }
  29. __set_current_state(TASK_RUNNING);
  30. return 0;
  31. }

程序6-1 ksoftirqd主功能函数

  1. out:
  2. local_irq_enable();
  3. return;
  4. softnet_break:
  5. __get_cpu_var(netdev_rx_stat).time_squeeze++;
  6. __raise_softirq_irqoff(NET_RX_SOFTIRQ);
  7. goto out;

程序6-2 net_rx_action

  1. void wakeup_softirqd(void)
  2. {
  3. /* Interrupts are disabled: no need to stop preemption */
  4. struct task_struct *tsk = __get_cpu_var(ksoftirqd);
  5. if (tsk && tsk->state != TASK_RUNNING)
  6. wake_up_process(tsk);
  7. }

程序6-3 wakeup_softirqd

还未解决的问题

set_current_state和 __set_current_state的区别

PF_NOFREEZE

cond_resched()

为什么在do_softirq开始处理软中断时要关闭硬件中断

参考

ULK

http://blog.csdn.net/hardy_2009/article/details/7383729 关于内核栈和中断栈的说明

Linux内核软中断的更多相关文章

  1. linux内核--软中断与tasklet

    硬件中断通常都需要在最短的时间内执行完毕,如果将所有硬件中断相关的处理都放在硬件中断处理程序中,那么就达不到这个目的. 通过linux提供的软中断和tasklet,可以将硬件中断处理程序中可以延迟处理 ...

  2. [Linux内核]软中断、tasklet、工作队列

    转自:http://www.cnblogs.com/li-hao/archive/2012/01/12/2321084.html 软中断.tasklet和工作队列并不是Linux内核中一直存在的机制, ...

  3. [Linux内核]软中断与硬中断

    转自:http://blog.csdn.net/zhangskd/article/details/21992933 本文主要内容:硬中断 / 软中断的原理和实现 内核版本:2.6.37 Author: ...

  4. Linux内核中的软中断、tasklet和工作队列具体解释

    [TOC] 本文基于Linux2.6.32内核版本号. 引言 软中断.tasklet和工作队列并非Linux内核中一直存在的机制,而是由更早版本号的内核中的"下半部"(bottom ...

  5. 浅析linux内核中timer定时器的生成和sofirq软中断调用流程(转自http://blog.chinaunix.net/uid-20564848-id-73480.html)

    浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_timer添加的定时器timer在内核的软中断中发生调用,__run_timers会spin_lock_irq(& ...

  6. 浅析linux内核中timer定时器的生成和sofirq软中断调用流程【转】

    转自:http://blog.chinaunix.net/uid-20564848-id-73480.html 浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_time ...

  7. 《深入理解Linux内核》软中断/tasklet/工作队列

    软中断.tasklet和工作队列并不是Linux内核中一直存在的机制,而是由更早版本的内核中的“下半部”(bottom half)演变而来.下半部的机制实际上包括五种,但2.6版本的内核中,下半部和任 ...

  8. 把握linux内核设计思想(三):下半部机制之软中断

    [版权声明:尊重原创.转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途]         中断处理程序以异步方式执行,其会打断其它重要代码,其执行时该中 ...

  9. Linux内核实现透视---软中断&Tasklet

    软中断 首先明确一个概念软中断(不是软件中断int n).总来来说软中断就是内核在启动时为每一个内核创建了一个特殊的进程,这个进程会不停的poll检查是否有软中断需要执行,如果需要执行则调用注册的接口 ...

随机推荐

  1. 【HTB系列】靶机Teacher的渗透测试详解

    出品|MS08067实验室(www.ms08067.com) 本文作者:大方子(Ms08067实验室核心成员) Kali: 10.10.14.50 靶机地址:10.10.10.153 先用nmap 对 ...

  2. SpringBoot(五):SpringBoot使用拦截器

    1.按照SpringMVC的方式编写一个拦截器: 2.配置一个类   implements WebMvcConfigurer 接口 为该类添加注解@Configuration  (等价于一个sprin ...

  3. localforage indexedDB如何使用索引

    简单介绍下localForage.localForage 是一个 JavaScript 库,通过简单类似 localStorage API 的异步存储来改进你的 Web 应用程序的离线体验.它能存储多 ...

  4. nacos集群

    本章分析一下nacos集群之间nacos服务器上线,下线原理 每5秒运行定时任务ServerListManager.ServerListUpdater获取新上线的节点或下线的节点 每2秒运行定时任务S ...

  5. 后端程序员之路 17、LaTeX公式

    之前的文章写了两个公式:d(x,y)=\sqrt{\sum_{i=1}^{n}(x_i-y_i)^2} H_x=-\sum_{i=1}^{n}p(x_i)\log_{2}{p(x_i)} LaTex ...

  6. Centos7安装Docker&镜像加速

    目录 Docker Docker安装 方式一 方式二 docker 镜像加速 Docker Docker安装 Docker安装 方式一 step1: 删除老版本(Uninstall old versi ...

  7. 优化自动化测试流程,使用 flask 开发一个 toy jenkins工具

    1.自动化 某一天你入职了一家高大上的科技公司,开心的做着软件测试的工作,每天点点点,下班就走,晚上陪女朋友玩王者,生活很惬意. 但是美好时光一般不长,这种生活很快被女主管打破.为了提升公司测试效率, ...

  8. Serverless Wordpress 系列建站教程(三)

    从前面两篇教程文章里,我们可以了解到 Serverless WordPress 的低门槛部署,免运维等功能优势.而建站场景中,开发者关注的另一个重点则是成本问题,Serverless 架构究竟如何计费 ...

  9. WPF 基础 - Binding 的 数据更新提醒

    WPF 作为一个专门的展示层技术,让程序员专注于逻辑层,让展示层永远处于逻辑层的从属地位: 这主要因为有 DataBinding 和配套的 Dependency Property 和 DataTemp ...

  10. 002-JVM部分

    JVM部分数据整理 一.运行时数据区域 Java运行时内存区域主要分为线程私有区域[程序计数器.虚拟机栈.本地方法区].线程共享区域[Java堆.方法区].直接内存(不受JVM GC管理) 1.线程私 ...