中断-softirq-tasklet-work queue(上)

简介: 中断-softirq-tasklet-work queue

前言

前面已经了解了底半部机制tasklet,工作队列,软中断,threaded_irq如何使用,这篇文章中将继续了解各种机制内部的调用关系。


软中断

初始化

软中断类型

//软中断的类型
enum
{
  HI_SOFTIRQ=0, /* 最高优先级软中断 */
  TIMER_SOFTIRQ,  /* Timer定时器软中断 */
  NET_TX_SOFTIRQ, /* 发送网络数据包软中断 */
  NET_RX_SOFTIRQ, /* 接收网络数据包软中断 */
  BLOCK_SOFTIRQ,  /* 块设备软中断 */
  BLOCK_IOPOLL_SOFTIRQ,/* 支持IO轮询的块设备软中断 */
  TASKLET_SOFTIRQ, /* tasklet软中断 */
  SCHED_SOFTIRQ, /* 进程调度及负载均衡的软中断 */
  HRTIMER_SOFTIRQ,//高精度定时器(已经放弃)
  RCU_SOFTIRQ,  //RCU相关的软中断
  /* Preferable RCU should always be the last softirq */
  NR_SOFTIRQS
};
#define SOFTIRQ_STOP_IDLE_MASK (~(1 << RCU_SOFTIRQ))
/* map softirq index to softirq name. update 'softirq_to_name' in
 * kernel/softirq.c when adding a new softirq.
 */
 /* 软中断描述符表,实际上就是一个全局的数组 */
extern const char * const softirq_to_name[NR_SOFTIRQS];
/* softirq mask and active fields moved to irq_cpustat_t in
 * asm/hardirq.h to get better cache usage.  KAO
 */
/* 软件中断描述符,只包含一个handler函数指针 */
struct softirq_action
{
  void  (*action)(struct softirq_action *);
};
asmlinkage void do_softirq(void);
asmlinkage void __do_softirq(void);
#ifdef __ARCH_HAS_DO_SOFTIRQ
void do_softirq_own_stack(void);
#else
static inline void do_softirq_own_stack(void)
{
  __do_softirq();
}
#endif
extern void open_softirq(int nr, void (*action)(struct softirq_action *));
extern void softirq_init(void);
extern void __raise_softirq_irqoff(unsigned int nr);
extern void raise_softirq_irqoff(unsigned int nr);
extern void raise_softirq(unsigned int nr);
/* 内核为每个CPU都创建了一个软中断处理内核线程 */
DECLARE_PER_CPU(struct task_struct *, ksoftirqd);

softirq_vec[]数组,类比硬件中断描述符表irq_desc[],通过软中断号可以找到对应的handler进行处理,比如图中的tasklet_action就是一个实际的handler函数;

软中断可以在不同的CPU上并行运行,在同一个CPU上只能串行执行;

每个CPU维护irq_cpustat_t状态结构,当某个软中断需要进行处理时,会将该结构体中的__softirq_pending字段或上1UL << XXX_SOFTIRQ;

软中断注册

软中断处理流程中,通过open_softirq接口来注册:

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
  softirq_vec[nr].action = action;//将软中断描述符表中对应描述符的handler函数指针指向对应的函数
}

软中断执行->中断处理后

软中断执行的入口是invoke_softirq:

static inline void invoke_softirq(void)
{
  if (!force_irqthreads) {
#ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK
    /*
     * We can safely execute softirq on the current stack if
     * it is the irq stack, because it should be near empty
     * at this stage.
     */
    __do_softirq();//中断未强制线程化
#else
    /*
     * Otherwise, irq_exit() is called on the task stack that can
     * be potentially deep already. So call softirq in its own stack
     * to prevent from any overrun.
     */
    do_softirq_own_stack();
#endif
  } else {//中断线程化
    wakeup_softirqd();//唤醒内核线程来处理软中断
  }
}

invoke_softirq函数中,根据中断处理是否线程化进行分类处理,如果中断已经进行了强制线程化处理(中断强制线程化,需要在启动的时候传入参数threadirqs),那么直接通过wakeup_softirqd唤醒内核线程来执行,否则的话则调用__do_softirq函数来处理;

在接收到中断信号后,处理器进行异常模式切换,并跳转到异常向量表处进行执行,关键的流程为:

el0_irq->irq_handler->handle_arch_irq(gic->handle_irq)->handle_domain_irq->__handle_domain_irq;

在__handle_domain_irq函数中,irq_enter和irq_exit分别用于来标识进入和离开硬件中断上下文处理,这个从preempt_count_add/preempt_count_sub来操作HARDIRQ_OFFSET可以看出来,这也对应到了上文中的Context描述图;

在离开硬件中断上下文后,如果!in_interrupt() && local_softirq_pending为真,则进行软中断处理。这个条件有两个含义:1)!in_interrupt()表明不能处在中断上下文中,这个范围包括in_nmi、in_irq、in_softirq(Bottom-half disable)、in_serving_softirq,凡是处于这几种状态下,软中断都不会被执行;2)local_softirq_pending不为0,表明有软中断处理请求;

invoke_softirq函数中,根据中断处理是否线程化进行分类处理,如果中断已经进行了强制线程化处理(中断强制线程化,需要在启动的时候传入参数threadirqs),那么直接通过wakeup_softirqd唤醒内核线程来执行,否则的话则调用__do_softirq函数来处理;

Linux内核会为每个CPU都创建一个内核线程ksoftirqd,通过smpboot_register_percpu_thread函数来完成,其中当内核线程运行时,在满足条件的情况下会执行run_ksoftirqd函数,如果此时有软中断处理请求,调用__do_softirq来进行处理;

核心处理

asmlinkage __visible void __do_softirq(void)
{
  unsigned long end = jiffies + MAX_SOFTIRQ_TIME;//软中断处理时间小于两毫秒;
  unsigned long old_flags = current->flags;
  int max_restart = MAX_SOFTIRQ_RESTART;//跳转到restart循环的次数不大于10次;
  struct softirq_action *h;
  bool in_hardirq;
  __u32 pending;
  int softirq_bit;
  /*
   * Mask out PF_MEMALLOC s current task context is borrowed for the
   * softirq. A softirq handled such as network RX might set PF_MEMALLOC
   * again if the socket is related to swap
   */
  current->flags &= ~PF_MEMALLOC;
  pending = local_softirq_pending();//读取__softirq_pending字段,用于判断是否有处理请求
  //可以类比于设备驱动中的状态寄存器,用于判断是否有软中断处理请求
  account_irq_enter_time(current);
  __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);//关闭button-half
  in_hardirq = lockdep_softirq_start();
restart:
  /* Reset the pending bitmask before enabling irqs */
  set_softirq_pending(0);//将__softirq_pending字段清零
  local_irq_enable();//关闭本地中断
  h = softirq_vec;
  while ((softirq_bit = ffs(pending))) {//软中断处理
  //循环读取状态位,直到处理完每一个软中断请求
    unsigned int vec_nr;
    int prev_count;
    h += softirq_bit - 1;
    vec_nr = h - softirq_vec;
    prev_count = preempt_count();
    kstat_incr_softirqs_this_cpu(vec_nr);
    trace_softirq_entry(vec_nr);
    h->action(h);
    trace_softirq_exit(vec_nr);
    if (unlikely(prev_count != preempt_count())) {
      pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
             vec_nr, softirq_to_name[vec_nr], h->action,
             prev_count, preempt_count());
      preempt_count_set(prev_count);
    }
    h++;
    pending >>= softirq_bit;
  }
/*
跳出while循环之后,再一次判断是否又有新的软中断请求到来(由于它可能被中断打断
,也就意味着可能有新的请求到来),有新的请求到来,则有三个条件判断,满足的话跳
转到restart处执行,否则调用wakeup_sotfirqd来唤醒内核线程来处理:
*/
  rcu_bh_qs();
  local_irq_disable();//打开本地中断
  pending = local_softirq_pending();//判断是否有新的请求到来
  if (pending) {
    if (time_before(jiffies, end) && !need_resched() &&
        --max_restart)
     /*
    time_before(jiffies, MAX_SOFTIRQ_TIME),软中断处理时间小于两毫秒;
    !need_resched,当前没有进程调度的请求
    */
      goto restart;//跳转到restart
    wakeup_softirqd();//唤醒内核线程来处理
  }
  lockdep_softirq_end(in_hardirq);
  account_irq_exit_time(current);
  __local_bh_enable(SOFTIRQ_OFFSET);//打开button_half
  WARN_ON_ONCE(in_interrupt());
  tsk_restore_flags(current, old_flags, PF_MEMALLOC);
}

local_softirq_pending函数用于读取__softirq_pending字段,可以类比于设备驱动中的状态寄存器,用于判断是否有软中断处理请求;

软中断处理时会关闭Bottom-half,处理完后再打开;

软中断处理时,会打开本地中断,处理完后关闭本地中断

time_before(jiffies, MAX_SOFTIRQ_TIME),软中断处理时间小于两毫秒;

!need_resched,当前没有进程调度的请求;

max_restart = MAX_SOFTIRQ_RESTART,跳转到restart循环的次数不大于10次;

这三个条件的判断,是基于延迟和公平的考虑,既要保证软中断尽快处理,又不能让软中断处理一直占据系统。

软中断的触发

软中断的触发通过raise_softirq接口:

void raise_softirq(unsigned int nr)
{
  unsigned long flags;
  local_irq_save(flags);//关闭本地中断
  raise_softirq_irqoff(nr);
  local_irq_restore(flags);//打开本地中断
}
inline void raise_softirq_irqoff(unsigned int nr)
{
  __raise_softirq_irqoff(nr);
  /*
   * If we're in an interrupt or softirq, we're done
   * (this also catches softirq-disabled code). We will
   * actually run the softirq once we return from
   * the irq or softirq.
   *
   * Otherwise we wake up ksoftirqd to make sure we
   * schedule the softirq soon.
   */
  if (!in_interrupt())//如果不在中断上下文中,那么唤醒内核线程来处理软中断
    wakeup_softirqd();
}

raise_softirq_irqoff函数中,最终会调用到or_softirq_pending,该函数会去读取本地CPU的irq_stat中__softirq_pending字段,然后将对应的软中断号给置位,表明有该软中断的处理请求

void __raise_softirq_irqoff(unsigned int nr)
{
  trace_softirq_raise(nr);
  or_softirq_pending(1UL << nr);//将__softirq_pending字段的比特位置位,请求软中断处理
}

raise_softirq_irqoff函数中,最终会调用到or_softirq_pending,该函数会去读取本地CPU的irq_stat中__softirq_pending字段,然后将对应的软中断号给置位,表明有该软中断的处理请求

目录
相关文章
|
6月前
|
Java API 调度
线程的中断(interrupt)机制
线程的中断(interrupt)机制
79 1
|
消息中间件 Unix Linux
进程通信 软中断 signal()解读
进程通信 软中断 signal()解读
queue Thread-Safe FIFO Implementation
queue Thread-Safe FIFO Implementation
中断-softirq-tasklet-work queue(下)
中断-softirq-tasklet-work queue
75 0
|
编译器 调度 C语言
RT-Thread记录(五、RT-Thread 临界区保护与FreeRTOS的比较)
本文聊聊临界区,以及RT-Thread对临界区的处理, 通过源码分析一下 RT-Thread 对临界区保护的实现以及与 FreeRTOS 处理的不同。
593 0
RT-Thread记录(五、RT-Thread 临界区保护与FreeRTOS的比较)
|
API 调度 C语言
RT-Thread记录(三、RT-Thread 线程操作函数及线程管理与FreeRTOS的比较)
前2课讲完了RT-Thread开发环境,启动流程,启动以后当然是开始跑线程了,那么自然我们得学会如何创建线程以及线程的有关操作。
801 0
RT-Thread记录(三、RT-Thread 线程操作函数及线程管理与FreeRTOS的比较)
|
Java 开发者
为什么不推荐使用 stop、suspend 方法中断线程?
我们知道像stop、suspend这几种中断或者阻塞线程的方法在较高java版本中已经被标记上了@Deprecated过期标签,那么为什么她们曾经登上了java的历史舞台而又渐渐的推出了舞台呢? 到底是人性的扭曲还是道德的沦丧呢,亦或是她们不思进取被取而代之呢,如果是被取而代之,那么取而代之的又是何方人也,本文我们将一探究竟。
246 0
为什么不推荐使用 stop、suspend 方法中断线程?