深入理解linux网络技术之驱动层

最近我又开始看这个《深入理解linux网络技术内幕》了。以前一直觉得这本书是一个巨无霸,昨天和前天各花了一点时间。大概是一直在搞内核的缘故吧!现在看起来倒不是很吃力了。大概看了1/3吧,虽然的确有点跑马观花了意味,但是自我感觉还是明白了主干的东西。《linux内核情景分析》里面对软中断讲得比较简略,而《深入》那书后边还图书馆了,所以导致我对软中断的理解很不够透彻。现在大致终结一下吧! 首先,软中断的执行是由ksoftirqd完成的,这是多个内核进程,每个CPU都有一个这样的进程,申明如下:

//linux-3.0.8/include/linux/interrupt.h  
DECLARE_PER_CPU(struct list_head [NR_SOFTIRQS], softirq_work_list);  
DECLARE_PER_CPU(struct task_struct *, ksoftirqd);  
  
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;  
  
struct softirq_action  
{  
    void    (*action)(struct softirq_action *);  
};  

softirq_action就是一个函数指针,NR_SOFTIRQS就是软中断的个数,不信你可以看看下边。

enum  
    {  
        HI_SOFTIRQ=0,  
        TIMER_SOFTIRQ,  
        NET_TX_SOFTIRQ,  
        NET_RX_SOFTIRQ,  
        BLOCK_SOFTIRQ,  
        BLOCK_IOPOLL_SOFTIRQ,  
        TASKLET_SOFTIRQ,  
        SCHED_SOFTIRQ,  
        HRTIMER_SOFTIRQ,  
        RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */  
      
        NR_SOFTIRQS  
    };  

在每个ksoftirqd线程中,它所干的是就去执行softirq_vec函数指针数组中的函数,相当于按优先级执行。每个软中断的函数都是在内核里定义好的。比如tasklet_hi_action(), tasklet_action(),net_rx_action()。当然,这还不算完。对于不同的软中断函数,执行的方法也不一样。就tasklet_hi_action()和tasklet_action()而言,它们各自有一个链表分别叫tasklet_vec,tasklet_hi_vec。这些链表单元结构都是tasklet_struct类型的。定义如下:

//linux-3.0.8/include/linux/interrupt.h  
struct tasklet_struct  
{  
    struct tasklet_struct *next;  
    unsigned long state;  
    atomic_t count;  
    void (*func)(unsigned long);  
    unsigned long data;  
};  
//linux-3.0.8/kernel/softirq.c  
    static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);  
    static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);  

看到那个func函数指针没有,这个就是链表最重要的元素。软中断函数tasklet_hi_action()和tasklet_action()的作用就是调用这个链表中的func函数。可以根据需要练添加tasklet_struct元素进入链表。对于net_rx_action(),这函数就是真正衔接中断的函数,算是所谓的“bottom half”了。它的工作机理其实和前面两个差不多。依然是一个链表,链表的表头在softnet_data。这是一个全局的数据结构。每一个CPU都有一个对应的soft_data,它包含一个headlist polllist的指针。polllist实际连接了napi_struct类型。而这个napi_struct类型是net_device息息相关。napi_struct中的poll指针就指向了具体的驱动模块中的poll()函数。到这里,我们终于找到了和上半部衔接的地方了。下面这是e100驱动的poll函数。

static int e100_poll(struct napi_struct *napi, int budget)  
{  
    struct nic *nic = container_of(napi, struct nic, napi);  
    unsigned int work_done = 0;  
  
    e100_rx_clean(nic, &work_done, budget);  
    e100_tx_clean(nic);  
  
    /* If budget not fully consumed, exit the polling mode */  
    if (work_done < budget) {  
        napi_complete(napi);  
        e100_enable_irq(nic);  
    }  
  
    return work_done;  
}  

看似清爽的函数其实并没有这么简单。主要的工作都是e100_rx_clean()在干。然后就是接收了,值得一提的是e100使用了DMA,然后调用的netif_receive_skb(skb)。这的确是历史性的一刻了。这标志着我们的数据经过千辛万苦终于是走出了驱动层,向L3迈进。

发表评论