看了网上的一些对此函数的解析,有些比较旧了。我在这分析一下linux-3.0.8的代码。
netif_receive_skb()中有RPS,我们不看了,直接看__netif_receive_skb()。
static int __netif_receive_skb(struct sk_buff *skb)
{
struct packet_type *ptype, *pt_prev;
rx_handler_func_t *rx_handler;
struct net_device *orig_dev;
struct net_device *null_or_dev;
bool deliver_exact = false;
int ret = NET_RX_DROP;
__be16 type;
// netdev_tstamp_prequeue设置为0,表示可能有些帧的延时;默认为1。
//net_timestamp_check()是设置skb的tstamp值,此值是记录接收包的时间
if (!netdev_tstamp_prequeue)
net_timestamp_check(skb);
trace_netif_receive_skb(skb);
/* netpoll 需要处理这个帧的话,会调用netpoll_rx 处理*/
if (netpoll_receive_skb(skb))
return NET_RX_DROP;
//给设备的接口序号赋值
if (!skb->skb_iif)
skb->skb_iif = skb->dev->ifindex;
orig_dev = skb->dev;
//下面说的复位主要是校准对应的指针
skb_reset_network_header(skb);//skb->network_header = skb->data - skb->head;网络层头
skb_reset_transport_header(skb);// skb->transport_header = skb->data;传输层
skb_reset_mac_len(skb);//skb->mac_len = skb->network_header - skb->mac_header;链路层头长,mac地址长度
pt_prev = NULL;
rcu_read_lock();
another_round:
__this_cpu_inc(softnet_data.processed);
if (skb->protocol == cpu_to_be16(ETH_P_8021Q)) {//如果是vlan要用的
skb = vlan_untag(skb);//vlan头数据的处理
if (unlikely(!skb))
goto out;
}
//…
//之前有说协议类型时有个ALL,这是给每个sniffer发一个
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);
/*
deliver_skb()就认为是pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
还记得我们之前注册的.func=vnic_rvc程序吧,不过我没有加到ptype_all.
*/
pt_prev = ptype;
}
}
//…
//rx_handler是特殊接收过程,例如网桥就利用这个。
rx_handler = rcu_dereference(skb->dev->rx_handler);
if (rx_handler) {
if (pt_prev) {
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL;
}
switch (rx_handler(&skb)) {
case RX_HANDLER_CONSUMED://不做进一步处理
goto out;
case RX_HANDLER_ANOTHER://skb->dev被改变,进行新一轮接收处理
goto another_round;
case RX_HANDLER_EXACT://强制传送
deliver_exact = true;
case RX_HANDLER_PASS://什么都不做,过时的SKB,好像没有rx_handler
break;
default:
BUG();
}
}
if (vlan_tx_tag_present(skb)) {
//vlan的不看了
}
null_or_dev = deliver_exact ? skb->dev : NULL;
//下面用到了存储我的ETH_P_VNIC的ptype_base[],就在此调用我的vnic_rcv.
type = skb->protocol;
list_for_each_entry_rcu(ptype,
&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
if (ptype->type == type &&
(ptype->dev == null_or_dev || ptype->dev == skb->dev ||
ptype->dev == orig_dev)) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
/*上面deliver_skb()可能多次被调用,是因为可以在多个驱动中add,例如我的vnic想接收ARP,我就加上
static struct packet_type arp _pack_type __read_mostly =
{
.type = cpu_to_be16(ETH_P_ARP),
.func = arp_rcv,
};
…
dev_add_pack(&arp _pack_type);
这样系统接到arp包会给vnic也发一份。
*/
if (pt_prev) {
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
} else {
atomic_long_inc(&skb->dev->rx_dropped);
kfree_skb(skb);
/* Jamal, now you will not able to escape explaining
* me how you were going to use this. :-)
*/
ret = NET_RX_DROP;
}
out:
rcu_read_unlock();
return ret;
}
在NAPI机制下,从硬件中断到vnic的vnic_rcv过程基本说完了。
下面我们看非NAPI。
DM9000 100M网卡
dm9000_interrupt是dm9000的中断函数,当中断发生时进入这里,根据ISR寄存器获取中断状态,如果为接受中断进调用dm9000_rx();
先看看mac帧格式
主要看dst_mac、src_mac、len、type、data
我们的类型就是ETH_P_VNIC.最后有4个字节表示fcs。Preamble\SFD一般由网卡自动生成。驱动代码不需要管。Fcs在接收时是外部发送过来的。
下面看dm9000_rx();
错误判断的代码省去了。
/* Move data from DM9000 */
//dev_alloc_skb()分配空间。+4就是指fcs。
if (GoodPacket &&
((skb = dev_alloc_skb(RxLen + 4)) != NULL)) {
//skb没有保存Preamble\SFD和length。所以dst_mac\src_mac和type总长度为14,所以为了16位对齐要移动2个字节。
skb_reserve(skb, 2);
//长度为RxLen-4,即忽略掉FCS。
rdptr = (u8 *) skb_put(skb, RxLen - 4);
/* Read received packet from RX SRAM */
(db->inblk)(db->io_data, rdptr, RxLen);//读数据
dev->stats.rx_bytes += RxLen;//接收字节计数
/* Pass to upper layer */
skb->protocol = eth_type_trans(skb, dev);//上次说过
if (dev->features & NETIF_F_RXCSUM) {//要接收校验就校验,不细看了
if ((((rxbyte & 0x1c) << 3) & rxbyte) == 0)
skb->ip_summed = CHECKSUM_UNNECESSARY;
else
skb_checksum_none_assert(skb);
}
netif_rx(skb);//上报,下面看
dev->stats.rx_packets++;//接收包计数
}
你可以先看看这篇文章
http://simohayha.iteye.com/blog/720850
介绍了rps对多cpu进行负载均衡的原理。我们就不考虑这个rps了。
下面看netif_rx.
int netif_rx(struct sk_buff *skb)
{
int ret;
/* if netpoll wants it, pretend we never saw it */
if (netpoll_rx(skb))//netpoll不管了
return NET_RX_DROP;
if (netdev_tstamp_prequeue)
net_timestamp_check(skb);//接收包时间戳
trace_netif_rx(skb);
#ifdef CONFIG_RPS
//RPS略去
#else
{
unsigned int qtail;
ret = enqueue_to_backlog(skb, get_cpu(), &qtail);
put_cpu();
}
#endif
return ret;
}
enqueue_to_backlog主要代码如下:(我们不考虑多cpu的情况了,如果你要看就看看上面的链接)
struct softnet_data *sd;
//…
sd = &per_cpu(softnet_data, cpu);
/*
在此文件开头还定义了一个每cpu变量。
DEFINE_PER_CPU_ALIGNED(struct softnet_data, softnet_data);
struct softnet_data包涵了一个struct napi_structbacklog;它的初始化如下:
sd->backlog.poll = process_backlog;
sd->backlog.weight = weight_p;
sd->backlog.gro_list = NULL;
sd->backlog.gro_count = 0;
*/
//…
//如果队列还没有超过netdev_max_backlog
if (skb_queue_len(&sd->input_pkt_queue) <= netdev_max_backlog) {
//如果当前的队列长度不为空,说明input_pkt_queue有没处理skb,软中断已触发。就不用再触发,只要把skb加入input_pkt_queue就可以了。
if (skb_queue_len(&sd->input_pkt_queue)) {
enqueue:
//skb加入input_pkt_queue中
__skb_queue_tail(&sd->input_pkt_queue, skb);
input_queue_tail_incr_save(sd, qtail);//rps有关
rps_unlock(sd);
local_irq_restore(flags);
return NET_RX_SUCCESS;
}
if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
if (!rps_ipi_queued(sd))
____napi_schedule(sd, &sd->backlog);//这里会调度net_rx_action.所谓的非napi,还是有napi的存在
}
goto enqueue;//跳到上面的enqueue,可以看到那里把skb加入到了input_pkt_queue.
}
net_rx_action之前说过了,现在我们看它调用的poll(),在此就是process_backlog
static int process_backlog(struct napi_struct *napi, int quota)
{
int work = 0;
struct softnet_data *sd = container_of(napi, struct softnet_data, backlog);
#ifdef CONFIG_RPS
//…
#endif
napi->weight = weight_p;//64
local_irq_disable();
while (work < quota) {//在此你可以看看上一篇对千兆网卡中napi的介绍
struct sk_buff *skb;
unsigned int qlen;
//__skb_dequeue()从队列中取出skb,在下面会把input_pkt_queue加入process_queue.
while ((skb = __skb_dequeue(&sd->process_queue))) {
local_irq_enable();
__netif_receive_skb(skb);//上面说过,调用它,我们就结束了
local_irq_disable();
input_queue_head_incr(sd);//rps有关
if (++work >= quota) {//超过最大流量
local_irq_enable();
return work;
}
}
rps_lock(sd);
qlen = skb_queue_len(&sd->input_pkt_queue);
if (qlen)
//连接两个SKB列表和重新初始化清空列表,把skb加入到process_queue,并把input_pkt_queue清空
skb_queue_splice_tail_init(&sd->input_pkt_queue,
&sd->process_queue);
if (qlen < quota - work) {
//…省去了
}
rps_unlock(sd);
}
local_irq_enable();
return work;
}