网络子系统在链路层的收发过程剖析(三)


4),poll函数

NONNAPI方式:

       这种方式对应该的poll函数为process_backlog

 

       struct softnet_data *queue = &__get_cpu_var(softnet_data);

       for (;;) {

 

              local_irq_disable();

              skb = __skb_dequeue(&queue->input_pkt_queue);

              local_irq_enable();

 

              netif_receive_skb(skb);

}

 

它首先找到当前CPUsoftnet_data结构,然后遍历其数据队SKB,并将数据上交netif_receive_skb(skb)处理。

 

NAPI方式:

       这种方式下,NIC驱动程序会提供自己的poll函数和私有接收队列。

intel 8255x系列网卡程序e100,它有在初始化的时候首先分配一个接收队列,而不像以上那种方式在接收到数据帧的时候再为其分配数据空间。这样,NAPIpoll函数在处理接收的时候,它遍历的是自己的私有队列:

 

       static int e100_poll(struct net_device *netdev, int *budget)

{

       e100_rx_clean(nic, &work_done, work_to_do);

       ……

}

 

static void e100_rx_clean(struct nic *nic, unsigned int *work_done,

       unsigned int work_to_do)

{

       …….

       /* Indicate newly arrived packets */

       for(rx = nic->rx_to_clean; rx->skb; rx = nic->rx_to_clean = rx->next) {

              int err = e100_rx_indicate(nic, rx, work_done, work_to_do);

              if(-EAGAIN == err) {

              ……

       }

              ……

}

 

static int e100_rx_indicate(struct nic *nic, struct rx *rx,

       unsigned int *work_done, unsigned int work_to_do)

{

       struct sk_buff *skb = rx->skb;

       struct rfd *rfd = (struct rfd *)skb->data;

       rfd_status = le16_to_cpu(rfd->status);

 

       /* Get actual data size */

       actual_size = le16_to_cpu(rfd->actual_size) & 0x3FFF;

 

       /* Pull off the RFD and put the actual data (minus eth hdr) */

       skb_reserve(skb, sizeof(struct rfd));

       skb_put(skb, actual_size);

       skb->protocol = eth_type_trans(skb, nic->netdev);

 

       netif_receive_skb(skb);

 

       return 0;

}

 

主要工作在e100_rx_indicate()中完成,这主要重设SKB的一些参数,然后跟process_backlog(),一样,最终调用netif_receive_skb(skb)

 

 

5),netif_receive_skb(skb)

这是一个辅助函数,用于在poll中处理接收到的帧。它主要是向各个已注册的协议处理例程发送一个SKB

每个协议的类型由一个packet_type结构表示:

 

struct packet_type {

       __be16                  type;       /* This is really htons(ether_type). */

       struct net_device    *dev;      /* NULL is wildcarded here        */

       int                  (*func) (struct sk_buff *,

                                    struct net_device *,

                                    struct packet_type *,

                                    struct net_device *);

       struct sk_buff        *(*gso_segment)(struct sk_buff *skb,

                                          int features);

       int                  (*gso_send_check)(struct sk_buff *skb);

       void               *af_packet_priv;

       struct list_head       list;

};

 

它的主要域为:type, 为要处理的协议

                       func, 为处理这个协议的例程

 

所用到的协议在系统或模块加载的时候初始化,如IP协议:

static struct packet_type ip_packet_type = {

       .type = __constant_htons(ETH_P_IP),

       .func = ip_rcv,

       .gso_send_check = inet_gso_send_check,

       .gso_segment = inet_gso_segment,

};

 

static int __init inet_init(void)

{

       ……

dev_add_pack(&ip_packet_type);

……

}

 

void dev_add_pack(struct packet_type *pt)

{

       int hash;

 

       spin_lock_bh(&ptype_lock);

       if (pt->type == htons(ETH_P_ALL)) {

              netdev_nit++;

              list_add_rcu(&pt->list, &ptype_all);

       } else {

              hash = ntohs(pt->type) & 15;

              list_add_rcu(&pt->list, &ptype_base[hash]);

       }

       spin_unlock_bh(&ptype_lock);

}

 

可以看到,dev_add_pack()是将一个协议类型结构链入某一个链表, 当协议类型为ETH_P_ALL时,它将被链入ptype_all链表,这个链表是用于sniffer这样一些程序的,它接收所有NIC收到的包。还有一个是HASH链表ptype_base,用于各种协议,它是一个16个元素的数组,dev_add_pack()会根据协议类型将这个packet_type链入相应的HASH链表中。

 

ptype_baseptype_all的组织结构如下,一个为HASH链表,一个为双向链表:

 

 

 

int netif_receive_skb(struct sk_buff *skb)

{

 

       list_for_each_entry_rcu(ptype, &ptype_all, list) {

              if (!ptype->dev || ptype->dev == skb->dev) {

                     if (pt_prev)

                            ret = deliver_skb(skb, pt_prev, orig_dev);

                     pt_prev = ptype;

              }

       }

 

       type = skb->protocol;

       list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {

              if (ptype->type == type &&

                  (!ptype->dev || ptype->dev == skb->dev)) {

                     if (pt_prev)

                            ret = deliver_skb(skb, pt_prev, orig_dev);

                     pt_prev = ptype;

              }

       }

 

       if (pt_prev) {

              ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);

       }

       return ret;

}

 

 

 

netif_receive_skb()的主要作用体现在两个遍历链表的操作中,其中之一为遍历ptype_all链,这些为注册到内核的一些sniffer,将上传给这些sniffer,另一个就是遍历ptype_base,这个就是具体的协议类型。假高如上图如示,当eth1接收到一个IP数据包时,它首先分别发送一份副本给两个ptype_all链表中的packet_type,它们都由package_rcv处理,然后再根据HASH值,在遍历另一个HASH表时,发送一份给类型为ETH_P_IP的类型,它由ip_rcv处理。如果这个链中还注册有其它IP层的协议,它也会同时发送一个副本给它。

 

其中,这个是由deliver_skb(skb, pt_prev, orig_dev)去完成的:

static __inline__ int deliver_skb(struct sk_buff *skb,

                              struct packet_type *pt_prev,

                              struct net_device *orig_dev)

{

       atomic_inc(&skb->users);

       return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);

}

 

可以看到,它只是一个包装函数,它只去执行相应packet_type里的func处理函数,如对于ETH_P_IP类型,由上面可以看到,它执行的就是ip_rcv了。

 

至此,一个以太网帧的链路层接收过程就全部完成,再下去就是网络层的处理了。

 

 

你可能感兴趣的:(网络子系统在链路层的收发过程剖析(三))