struct net_device loopback_dev = { .name = "lo", .mtu = (16 * 1024) + 20 + 20 + 12, .hard_start_xmit = loopback_xmit, .hard_header = eth_header, .hard_header_cache = eth_header_cache, .header_cache_update = eth_header_cache_update, .hard_header_len = ETH_HLEN, /* 14 */ .addr_len = ETH_ALEN, /* 6 */ .tx_queue_len = 0, .type = ARPHRD_LOOPBACK, /* 0x0001*/ .rebuild_header = eth_rebuild_header, .flags = IFF_LOOPBACK, .features = NETIF_F_SG|NETIF_F_FRAGLIST |NETIF_F_NO_CSUM|NETIF_F_HIGHDMA |NETIF_F_LLTX, .ethtool_ops = &loopback_ethtool_ops, };
/* Setup and register the of the LOOPBACK device. */ int __init loopback_init(void) { struct net_device_stats *stats;
/* Can survive without statistics */ stats = kmalloc(sizeof(struct net_device_stats), GFP_KERNEL); if (stats) { memset(stats, 0, sizeof(struct net_device_stats)); loopback_dev.priv = stats; loopback_dev.get_stats = &get_stats; } return register_netdev(&loopback_dev); };
|
Linux内核用结构体struct net_device表示一个网络设备接口,该结构体的成员hard_start_xmit是一个函数指针,用于完成数据报在网络上的发送工作,其原型是:
int (*hard_start_xmit)( struct sk_buff *skb, struct net_device *dev );
skb是待发送的数据缓冲区,dev是该网络设备接口本身的一个指针。
环回设备接口由于是把数据报发给本机,所以其发送数据报函数比较特殊,它把skb稍加处理后,又转回给协议栈的数据报接收函数netif_rx。
其发送函数的函数名loopback_xmit。
二. 我们看一下loopback_xmit()函数的源码:
static int loopback_xmit(struct sk_buff *skb, struct net_device *dev) { struct net_device_stats *lb_stats;
skb_orphan(skb); skb->protocol=eth_type_trans(skb,dev); skb->dev=dev; #ifndef LOOPBACK_MUST_CHECKSUM skb->ip_summed = CHECKSUM_UNNECESSARY; #endif
if (skb_shinfo(skb)->tso_size) {
BUG_ON(skb->protocol != htons(ETH_P_IP));
BUG_ON(skb->nh.iph->protocol != IPPROTO_TCP);
emulate_large_send_offload(skb);
return 0;
}
dev->last_rx = jiffies; lb_stats = &per_cpu(loopback_stats, get_cpu()); lb_stats->rx_bytes += skb->len; lb_stats->tx_bytes += skb->len; lb_stats->rx_packets++; lb_stats->tx_packets++; put_cpu(); netif_rx(skb); return(0); }
|
首先,loopback_xmit调用skb_orphan把skb孤立,使它跟发送socket和协议栈不再有任何联系,也即对本机来说,这个skb的数据内容已经发送出去了,而skb相当于已经被释放掉了。
static inline void skb_orphan(struct sk_buff *skb) { if (skb->destructor) skb->destructor(skb); skb->destructor = NULL; skb->sk = NULL; }
|
skb_orphan所做的实际事情是,首先从skb->sk(发送这个skb的那个socket)的sk_wmem_alloc减去skb->truesize,也即从socket的已提交发送队列的字节数中减去这个skb,表示这个skb已经发送出去了, 同时,如果有进程在这个socket上写等待,则唤醒这些进程继续发送数据报,然后把socket的引用计数减1,最后,令 sk->destructor和skb->sk都为NULL,使skb完全孤立。实际上,对于环回设备接口来说,数据的发送工作至此已经全部完成,接下来,只要把这个实际上还未被释放的skb传回给协议栈的接收函数即可。
如果 skb_shinfo(skb)->tso_size不为0(TSO是某些网络设备能够在传输之前把一帧大小分成更小的几帧(大小一般为MTU大小。它的实现是通过把TCP负载拆分几段,然后分别在段上加上同样的IP头。),就会调用emulate_large_send_offload()函数。emulate_large_send_offload()会调用netif_rx()函数接受数据。
如果tso_size 为0,则更新一下统计值,调用netif_rx()函数。
阅读(5284) | 评论(1) | 转发(2) |