Chinaunix首页 | 论坛 | 博客
  • 博客访问: 573174
  • 博文数量: 117
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 359
  • 用 户 组: 普通用户
  • 注册时间: 2011-03-13 21:58
个人简介

爱上香烟

文章分类

全部博文(117)

文章存档

2018年(3)

2017年(8)

2016年(65)

2015年(41)

我的朋友

分类: C/C++

2017-05-31 19:56:53

原文地址:Linux下Loopback流程分析 作者:rainballdh

.我们来看一下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_orphanskb孤立,使它跟发送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->destructorskb->sk都为NULL,使skb完全孤立。实际上,对于环回设备接口来说,数据的发送工作至此已经全部完成,接下来,只要把这个实际上还未被释放的skb传回给协议栈的接收函数即可。

 

如果 skb_shinfo(skb)->tso_size不为0TSO是某些网络设备能够在传输之前把一帧大小分成更小的几帧(大小一般为MTU大小。它的实现是通过把TCP负载拆分几段,然后分别在段上加上同样的IP头。),就会调用emulate_large_send_offload()函数。emulate_large_send_offload()会调用netif_rx()函数接受数据。

 

如果tso_size 为0,则更新一下统计值,调用netif_rx()函数。

阅读(1664) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~