Linux 內核網絡收包角度—淺入中斷 -2-

上文 Linux 內核網絡收包角度——淺入中斷 (1) 主要從針對 e1000 網卡收包開始,分析了硬中斷註冊、處理過程,網卡收包後硬中斷的過程非常簡潔,處理過程比較簡單,**主****要是將網卡的 napi->poll_list 加入到 CPU 的 softnet_data->poll_list 裏,然後發起軟中斷,就結束了,下面的就交給軟中斷進行數據包的接收處理,所以收包任務最大程度地交給軟中斷處理,最大程度簡化硬中斷處理。**總結以上描述中,上篇博文的具體細節步驟:

上文 Linux 內核網絡收包角度——淺入中斷 (1) 重點在於通過網卡收包分析了硬中斷,本文從網絡收包角度分析和學習軟中****斷過程。

軟中斷的種類、定義 (Kernel 4.15)

enum
{
 HI_SOFTIRQ=0,
 TIMER_SOFTIRQ,
 NET_TX_SOFTIRQ,
 NET_RX_SOFTIRQ,
 BLOCK_SOFTIRQ,
 IRQ_POLL_SOFTIRQ,
 TASKLET_SOFTIRQ,
 SCHED_SOFTIRQ,
 HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the
       numbering. Sigh! */
 RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

 NR_SOFTIRQS
}

以上是內核默認定義的幾種軟中斷,硬中斷是在 CPU 每個指令週期後,會去判斷是否有硬中斷產生,根據硬中斷號找到對應的 IRQ Number,執行對應的中斷服務例程,而軟中斷會有內核線程,輪詢一組標誌位,如果標誌位有值,那去這個標誌位對應的軟中斷向量表,找到中斷處理函數執行。

軟中斷的註冊

全局的軟中斷向量數組,即軟中斷描述符表

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

const char * const softirq_to_name[NR_SOFTIRQS] = {
 "HI""TIMER""NET_TX""NET_RX""BLOCK""IRQ_POLL",
 "TASKLET""SCHED""HRTIMER""RCU"
}

數組中的內容就是軟中斷觸發時的鉤子函數:

struct softirq_action
{
 void (*action)(struct softirq_action *);
}

如網絡收發包的軟中斷處理函數在網絡子系統初始化時註冊的:

static int __init net_dev_init(void)
{
  ......
 open_softirq(NET_TX_SOFTIRQ, net_tx_action);
 open_softirq(NET_RX_SOFTIRQ, net_rx_action);
  ......
}

註冊軟中斷回調函數:

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

軟中斷在內核中的整體如下所示,pending 爲軟中斷標誌位

其中標誌位在內核中使用 irq_cpustat 來表示:

typedef struct {
 unsigned int __softirq_pending;
#ifdef CONFIG_SMP
 unsigned int ipi_irqs[NR_IPI];
#endif
} ____cacheline_aligned irq_cpustat_t;
}

其中__softirq_pending 爲軟中斷標誌位,該類型爲 int 類型,最多支持 32 位軟中斷,ipi_rrq 位 cpu 與 cpu 之間的中斷。

在 Linux 內核中,定義 NR_CPUS 個該結構,即每個 CPU 有一個 32bit 的位圖,來維護本 cpu 上的軟中斷是否激活

irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;

軟中斷的激活

在上文 Linux 內核網絡收包角度——淺入中斷 (1) 中,硬中斷的中斷服務例程 e1000_intr 執行:

e1000_intr->__napi_schedule:

void __napi_schedule(struct napi_struct *n)
{
 unsigned long flags;

 local_irq_save(flags);//禁用中斷
 ____napi_schedule(this_cpu_ptr(&softnet_data), n);
 local_irq_restore(flags);//恢復中斷
}

__napi_schedule(this_cpu_ptr(&softnet_data),n):

static inline void ____napi_schedule(struct softnet_data *sd,
         struct napi_struct *napi)
{
 list_add_tail(&napi->poll_list, &sd->poll_list);
 __raise_softirq_irqoff(NET_RX_SOFTIRQ);
}

最終調用__raise_soft_irqoff 設置軟中斷標誌位,nr 爲 NET_RX_SOFTIRQ

void __raise_softirq_irqoff(unsigned int nr)
{
 trace_softirq_raise(nr);
 or_softirq_pending(1UL << nr);
}

可以看到在執行__raise_soft_irqoff 函數前,在__napi_schedule 中,先是通過 local_irq_save(flags) 禁用了中斷,因爲置位位圖是一個競爭操作,所有硬中斷都可以做,所以要保證關中斷的情況下完成,等 pending 置位結束後調用 local_irq_restore(flags) 恢復中斷。

內核線程 ksoftirqd 是如何監測到軟中斷髮生的:

static void run_ksoftirqd(unsigned int cpu)
{
 local_irq_disable();
 if (local_softirq_pending()) {
  /*
   * We can safely run softirq on inline stack, as we are not deep
   * in the task stack here.
   */
  __do_softirq();
  local_irq_enable();
  cond_resched_rcu_qs();
  return;
 }
 local_irq_enable();
}

上面函數先關中斷查看本 cpu 的 pending 置位情況,如果有則進行進一步軟中斷處理。

軟中斷的入口函數__do_softirq:

asmlinkage __visible void __softirq_entry __do_softirq(void)
{
  ......
 pending = local_softirq_pending();  //將32位的pending集合取出來
 account_irq_enter_time(current);

 __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
 in_hardirq = lockdep_softirq_start();

restart:

 set_softirq_pending(0);//pending清空

 local_irq_enable();  //使能中斷

 h = softirq_vec; //軟中斷處理向量表,裏面是軟中斷處理函數指針

 while ((softirq_bit = ffs(pending))) { //獲取最高位置1的位數
  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;
 }

 rcu_bh_qs();
 local_irq_disable();//關中斷

 pending = local_softirq_pending(); //重新獲取pending集合(開中斷到關中斷的過程中可能有新的硬中斷到來,這時候需要重新處理一遍)
 if (pending) {  //如果pengding中又有軟中斷置位
  if (time_before(jiffies, end) && !need_resched() &&
      --max_restart)
   goto restart;

  wakeup_softirqd(); //喚醒軟中斷處理線程處理
 }

 lockdep_softirq_end(in_hardirq);
 account_irq_exit_time(current);
 __local_bh_enable(SOFTIRQ_OFFSET);
 WARN_ON_ONCE(in_interrupt());
 current_restore_flags(old_flags, PF_MEMALLOC);
}

對於數據包接收 h->action() 將執行:網絡子系統註冊的的回調函數 net_rx_action:

static int __init net_dev_init(void)
{
  ......
 open_softirq(NET_TX_SOFTIRQ, net_tx_action);
 open_softirq(NET_RX_SOFTIRQ, net_rx_action);
  ......
}

關於 net_rx_action 涉及到軟中斷處理中進一步使用 NAPI 機制收包,在接下來的文章中進行分析和學習。

參考:

https://zhuanlan.zhihu.com/p/363225092

https://zhuanlan.zhihu.com/p/363225717

https://www.i4k.xyz/article/weixin_38878510/109024909

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/H_7pq8Ea3nCAEJ3GjAsj0g