图解Linux网络包接收过程( 五 )

igb_write_itr只是记录一下硬件中断频率(据说目的是在减少对CPU的中断频率时用到) 。顺着napi_schedule调用一路跟踪下去,__napi_schedule=>____napi_schedule
/* Called with irq disabled */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);}这里我们看到,list_add_tail修改了CPU变量softnet_data里的poll_list,将驱动napi_struct传过来的poll_list添加了进来 。其中softnet_data中的poll_list是一个双向列表,其中的设备都带有输入帧等着被处理 。紧接着__raise_softirq_irqoff触发了一个软中断NET_RX_SOFTIRQ,这个所谓的触发过程只是对一个变量进行了一次或运算而已 。
void __raise_softirq_irqoff(unsigned int nr){    trace_softirq_raise(nr);    or_softirq_pending(1UL << nr);}//file: include/linux/irq_cpustat.h#define or_softirq_pending(x)  (local_softirq_pending() |= (x))我们说过,Linux在硬中断里只完成简单必要的工作,剩下的大部分的处理都是转交给软中断的 。通过上面代码可以看到,硬中断处理过程真的是非常短 。只是记录了一个寄存器,修改了一下下CPU的poll_list,然后发出个软中断 。就这么简单,硬中断工作就算是完成了 。
3.2 ksoftirqd内核线程处理软中断

图解Linux网络包接收过程

文章插图
 
图9 ksoftirqd内核线程
内核线程初始化的时候,我们介绍了ksoftirqd中两个线程函数ksoftirqd_should_run和run_ksoftirqd 。其中ksoftirqd_should_run代码如下:
static int ksoftirqd_should_run(unsigned int cpu){    return local_softirq_pending();}#define local_softirq_pending()   __IRQ_STAT(smp_processor_id(), __softirq_pending)这里看到和硬中断中调用了同一个函数local_softirq_pending 。使用方式不同的是硬中断位置是为了写入标记,这里仅仅只是读取 。如果硬中断中设置了NET_RX_SOFTIRQ,这里自然能读取的到 。接下来会真正进入线程函数中run_ksoftirqd处理:
static void run_ksoftirqd(unsigned int cpu){    local_irq_disable();    if (local_softirq_pending()) {        __do_softirq();        rcu_note_context_switch(cpu);        local_irq_enable();        cond_resched();        return;    }    local_irq_enable();}在__do_softirq中,判断根据当前CPU的软中断类型,调用其注册的action方法 。
asmlinkage void __do_softirq(void){    do {        if (pending & 1) {            unsigned int vec_nr = h - softirq_vec;            int prev_count = preempt_count();            ...            trace_softirq_entry(vec_nr);            h->action(h);            trace_softirq_exit(vec_nr);            ...        }        h++;        pending >>= 1;    } while (pending);}在网络子系统初始化小节,我们看到我们为NET_RX_SOFTIRQ注册了处理函数net_rx_action 。所以net_rx_action函数就会被执行到了 。
这里需要注意一个细节,硬中断中设置软中断标记,和ksoftirq的判断是否有软中断到达,都是基于smp_processor_id()的 。这意味着只要硬中断在哪个CPU上被响应,那么软中断也是在这个CPU上处理的 。所以说,如果你发现你的Linux软中断CPU消耗都集中在一个核上的话,做法是要把调整硬中断的CPU亲和性,来将硬中断打散到不同的CPU核上去 。
我们再来把精力集中到这个核心函数net_rx_action上来 。
static void net_rx_action(struct softirq_action *h){    struct softnet_data *sd = &__get_cpu_var(softnet_data);    unsigned long time_limit = jiffies + 2;    int budget = netdev_budget;    void *have;    local_irq_disable();    while (!list_empty(&sd->poll_list)) {        ......        n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);        work = 0;        if (test_bit(NAPI_STATE_SCHED, &n->state)) {            work = n->poll(n, weight);            trace_napi_poll(n);        }        budget -= work;    }}


推荐阅读