您的位置:首页 > 理论基础 > 计算机网络

Linux内核--网络栈实现分析(七)--数据包的传递过程(下)

2013-04-13 13:28 579 查看
本文分析基于Linux Kernel 1.2.13

原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7545855

更多请查看专栏,地址http://blog.csdn.net/column/details/linux-kernel-net.html

作者:闫明

注:标题中的”(上)“,”(下)“表示分析过程基于数据包的传递方向:”(上)“表示分析是从底层向上分析、”(下)“表示分析是从上向下分析。

在博文Linux内核--网络栈实现分析(二)--数据包的传递过程(上)中分析了数据包从网卡设备经过驱动链路层,网络层,传输层到应用层的过程。

本文就分析一下本机产生数据是如何通过传输层,网络层到达物理层的。

综述来说,数据流程图如下:



一、应用层

应用层可以通过系统调用或文件操作来调用内核函数,BSD层的sock_write()函数会调用INET层的inet_wirte()函数。

[cpp] view
plaincopy

/* 

 *  Write data to a socket. We verify that the user area ubuf..ubuf+size-1 is 

 *  readable by the user process. 

 */  

  

static int sock_write(struct inode *inode, struct file *file, char *ubuf, int size)  

{  

    struct socket *sock;  

    int err;  

      

    if (!(sock = socki_lookup(inode)))   

    {  

        printk("NET: sock_write: can't find socket for inode!\n");  

        return(-EBADF);  

    }  

  

    if (sock->flags & SO_ACCEPTCON)   

        return(-EINVAL);  

      

    if(size<0)  

        return -EINVAL;  

    if(size==0)  

        return 0;  

          

    if ((err=verify_area(VERIFY_READ,ubuf,size))<0)  

        return err;  

    return(sock->ops->write(sock, ubuf, size,(file->f_flags & O_NONBLOCK)));  

}  

INET层会调用具体传输层协议的write函数,该函数是通过调用本层的inet_send()函数实现功能的,inet_send()函数的UDP协议对应的函数为udp_write()

[cpp] view
plaincopy

static int inet_send(struct socket *sock, void *ubuf, int size, int noblock,   

           unsigned flags)  

{  

    struct sock *sk = (struct sock *) sock->data;  

    if (sk->shutdown & SEND_SHUTDOWN)   

    {  

        send_sig(SIGPIPE, current, 1);  

        return(-EPIPE);  

    }  

    if(sk->err)  

        return inet_error(sk);  

    /* We may need to bind the socket. */  

    if(inet_autobind(sk)!=0)  

        return(-EAGAIN);  

    return(sk->prot->write(sk, (unsigned char *) ubuf, size, noblock, flags));  

}  

  

static int inet_write(struct socket *sock, char *ubuf, int size, int noblock)  

{  

    return inet_send(sock,ubuf,size,noblock,0);  

}  

二、传输层

在传输层udp_write()函数调用本层的udp_sendto()函数完成功能。

[cpp] view
plaincopy

/* 

 *  In BSD SOCK_DGRAM a write is just like a send. 

 */  

  

static int udp_write(struct sock *sk, unsigned char *buff, int len, int noblock,  

      unsigned flags)  

{  

    return(udp_sendto(sk, buff, len, noblock, flags, NULL, 0));  

}  

udp_send()函数完成sk_buff结构相应的设置和报头的填写后会调用udp_send()来发送数据。具体的实现过程后面会详细分析。

而在udp_send()函数中,最后会调用ip_queue_xmit()函数,将数据包下放的网络层。

下面是udp_prot定义:

[cpp] view
plaincopy

struct proto udp_prot = {  

    sock_wmalloc,  

    sock_rmalloc,  

    sock_wfree,  

    sock_rfree,  

    sock_rspace,  

    sock_wspace,  

    udp_close,  

    udp_read,  

    udp_write,  

    udp_sendto,  

    udp_recvfrom,  

    ip_build_header,  

    udp_connect,  

    NULL,  

    ip_queue_xmit,  

    NULL,  

    NULL,  

    NULL,  

    udp_rcv,  

    datagram_select,  

    udp_ioctl,  

    NULL,  

    NULL,  

    ip_setsockopt,  

    ip_getsockopt,  

    128,  

    0,  

    {NULL,},  

    "UDP",  

    0, 0  

};  

[cpp] view
plaincopy

static int udp_send(struct sock *sk, struct sockaddr_in *sin,  

     unsigned char *from, int len, int rt)  

{  

    struct sk_buff *skb;  

    struct device *dev;  

    struct udphdr *uh;  

    unsigned char *buff;  

    unsigned long saddr;  

    int size, tmp;  

    int ttl;  

    

    /*  

     *  Allocate an sk_buff copy of the packet. 

     */  

       

    ........................  

  

    /* 

     *  Now build the IP and MAC header.  

     */  

       

    ..........................  

    /* 

     *  Fill in the UDP header.  

     */  

       

    ..............................  

  

    /* 

     *  Copy the user data.  

     */  

       

    memcpy_fromfs(buff, from, len);  

  

    /* 

     *  Set up the UDP checksum.  

     */  

       

    udp_send_check(uh, saddr, sin->sin_addr.s_addr, skb->len - tmp, sk);  

  

    /*  

     *  Send the datagram to the interface.  

     */  

       

    udp_statistics.UdpOutDatagrams++;  

       

    sk->prot->queue_xmit(sk, dev, skb, 1);  

    return(len);  

}  

三、网络层

 在网络层,函数ip_queue_xmit()的功能是将数据包进行一系列复杂的操作,比如是检查数据包是否需要分片,是否是多播等一系列检查,最后调用dev_queue_xmit()函数发送数据。

[cpp] view
plaincopy

/* 

 * Queues a packet to be sent, and starts the transmitter 

 * if necessary.  if free = 1 then we free the block after 

 * transmit, otherwise we don't. If free==2 we not only 

 * free the block but also don't assign a new ip seq number. 

 * This routine also needs to put in the total length, 

 * and compute the checksum 

 */  

  

void ip_queue_xmit(struct sock *sk, struct device *dev,  

          struct sk_buff *skb, int free)  

{  

    struct iphdr *iph;  

    unsigned char *ptr;  

  

    /* Sanity check */  

    ............  

    /* 

     *  Do some book-keeping in the packet for later 

     */  

  

  

    ...........  

  

    /* 

     *  Find the IP header and set the length. This is bad 

     *  but once we get the skb data handling code in the 

     *  hardware will push its header sensibly and we will 

     *  set skb->ip_hdr to avoid this mess and the fixed 

     *  header length problem 

     */  

  

    ..............  

    /* 

     *  No reassigning numbers to fragments... 

     */  

  

    if(free!=2)  

        iph->id      = htons(ip_id_count++);  

    else  

        free=1;  

  

    /* All buffers without an owner socket get freed */  

    if (sk == NULL)  

        free = 1;  

  

    skb->free = free;  

  

    /* 

     *  Do we need to fragment. Again this is inefficient. 

     *  We need to somehow lock the original buffer and use 

     *  bits of it. 

     */  

  

    ................  

  

    /* 

     *  Add an IP checksum 

     */  

  

    ip_send_check(iph);  

  

    /* 

     *  Print the frame when debugging 

     */  

  

    /* 

     *  More debugging. You cannot queue a packet already on a list 

     *  Spot this and moan loudly. 

     */  

    .......................  

  

    /* 

     *  If a sender wishes the packet to remain unfreed 

     *  we add it to his send queue. This arguably belongs 

     *  in the TCP level since nobody else uses it. BUT 

     *  remember IPng might change all the rules. 

     */  

  

    ......................  

    /* 

     *  If the indicated interface is up and running, send the packet. 

     */  

       

    ip_statistics.IpOutRequests++;  

        .............................  

    .............................  

        if((dev->flags&IFF_BROADCAST) && iph->daddr==dev->pa_brdaddr && !(dev->flags&IFF_LOOPBACK))  

        ip_loopback(dev,skb);  

          

    if (dev->flags & IFF_UP)  

    {  

        /* 

         *  If we have an owner use its priority setting, 

         *  otherwise use NORMAL 

         */  

  

        if (sk != NULL)  

        {  

            dev_queue_xmit(skb, dev, sk->priority);  

        }  

        else  

        {  

            dev_queue_xmit(skb, dev, SOPRI_NORMAL);  

        }  

    }  

    else  

    {  

        ip_statistics.IpOutDiscards++;  

        if (free)  

            kfree_skb(skb, FREE_WRITE);  

    }  

}  

四、驱动层(链路层)

在函数中,函数调用会调用具体设备的发送函数来发送数据包

dev->hard_start_xmit(skb, dev);

具体设备的发送函数在网络初始化的时候已经设置了。

这里以8390网卡为例来说明驱动层的工作原理,在net/drivers/8390.c中函数ethdev_init()函数中设置如下:

[cpp] view
plaincopy

/* Initialize the rest of the 8390 device structure. */  

int ethdev_init(struct device *dev)  

{  

    if (ei_debug > 1)  

        printk(version);  

      

    if (dev->priv == NULL) {//申请私有空间  

        struct ei_device *ei_local;//8390网卡设备的结构体  

          

        dev->priv = kmalloc(sizeof(struct ei_device), GFP_KERNEL);//申请内核内存空间  

        memset(dev->priv, 0, sizeof(struct ei_device));  

        ei_local = (struct ei_device *)dev->priv;  

#ifndef NO_PINGPONG  

        ei_local->pingpong = 1;  

#endif  

    }  

      

    /* The open call may be overridden by the card-specific code. */  

    if (dev->open == NULL)  

        dev->open = &ei_open;//设备的打开函数  

    /* We should have a dev->stop entry also. */  

    dev->hard_start_xmit = &ei_start_xmit;//设备的发送函数,定义在8390.c中  

    dev->get_stats   = get_stats;  

#ifdef HAVE_MULTICAST  

    dev->set_multicast_list = &set_multicast_list;  

#endif  

  

    ether_setup(dev);  

          

    return 0;  

}  

驱动中的发送函数比较复杂,和硬件关系紧密,这里不再详细分析。

这样就大体分析了下网络数据从应用层到物理层的数据通路,后面会详细分析。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐