(4),poll函数
NON—NAPI方式:
这种方式对应该的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);
}
它首先找到当前CPU的softnet_data结构,然后遍历其数据队SKB,并将数据上交netif_receive_skb(skb)处理。
NAPI方式:
这种方式下,NIC驱动程序会提供自己的poll函数和私有接收队列。
如intel 8255x系列网卡程序e100,它有在初始化的时候首先分配一个接收队列,而不像以上那种方式在接收到数据帧的时候再为其分配数据空间。这样,NAPI的poll函数在处理接收的时候,它遍历的是自己的私有队列:
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_base与ptype_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了。
至此,一个以太网帧的链路层接收过程就全部完成,再下去就是网络层的处理了。