一.我们来看一下Loopback的初始化:
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()函数。