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


    本文分析基于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()函数。

    1. /* 
    2.  *  Write data to a socket. We verify that the user area ubuf..ubuf+size-1 is 
    3.  *  readable by the user process. 
    4.  */  
    5.   
    6. static int sock_write(struct inode *inode, struct file *file, char *ubuf, int size)  
    7. {  
    8.     struct socket *sock;  
    9.     int err;  
    10.       
    11.     if (!(sock = socki_lookup(inode)))   
    12.     {  
    13.         printk("NET: sock_write: can't find socket for inode!\n");  
    14.         return(-EBADF);  
    15.     }  
    16.   
    17.     if (sock->flags & SO_ACCEPTCON)   
    18.         return(-EINVAL);  
    19.       
    20.     if(size<0)  
    21.         return -EINVAL;  
    22.     if(size==0)  
    23.         return 0;  
    24.           
    25.     if ((err=verify_area(VERIFY_READ,ubuf,size))<0)  
    26.         return err;  
    27.     return(sock->ops->write(sock, ubuf, size,(file->f_flags & O_NONBLOCK)));  
    28. }  

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

    1. static int inet_send(struct socket *sock, void *ubuf, int size, int noblock,   
    2.            unsigned flags)  
    3. {  
    4.     struct sock *sk = (struct sock *) sock->data;  
    5.     if (sk->shutdown & SEND_SHUTDOWN)   
    6.     {  
    7.         send_sig(SIGPIPE, current, 1);  
    8.         return(-EPIPE);  
    9.     }  
    10.     if(sk->err)  
    11.         return inet_error(sk);  
    12.     /* We may need to bind the socket. */  
    13.     if(inet_autobind(sk)!=0)  
    14.         return(-EAGAIN);  
    15.     return(sk->prot->write(sk, (unsigned char *) ubuf, size, noblock, flags));  
    16. }  
    17.   
    18. static int inet_write(struct socket *sock, char *ubuf, int size, int noblock)  
    19. {  
    20.     return inet_send(sock,ubuf,size,noblock,0);  
    21. }  

    二、传输层

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

    1. /* 
    2.  *  In BSD SOCK_DGRAM a write is just like a send. 
    3.  */  
    4.   
    5. static int udp_write(struct sock *sk, unsigned char *buff, int len, int noblock,  
    6.       unsigned flags)  
    7. {  
    8.     return(udp_sendto(sk, buff, len, noblock, flags, NULL, 0));  
    9. }  

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

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

    下面是udp_prot定义:

    1. struct proto udp_prot = {  
    2.     sock_wmalloc,  
    3.     sock_rmalloc,  
    4.     sock_wfree,  
    5.     sock_rfree,  
    6.     sock_rspace,  
    7.     sock_wspace,  
    8.     udp_close,  
    9.     udp_read,  
    10.     udp_write,  
    11.     udp_sendto,  
    12.     udp_recvfrom,  
    13.     ip_build_header,  
    14.     udp_connect,  
    15.     NULL,  
    16.     ip_queue_xmit,  
    17.     NULL,  
    18.     NULL,  
    19.     NULL,  
    20.     udp_rcv,  
    21.     datagram_select,  
    22.     udp_ioctl,  
    23.     NULL,  
    24.     NULL,  
    25.     ip_setsockopt,  
    26.     ip_getsockopt,  
    27.     128,  
    28.     0,  
    29.     {NULL,},  
    30.     "UDP",  
    31.     0, 0  
    32. };  

    1. static int udp_send(struct sock *sk, struct sockaddr_in *sin,  
    2.      unsigned char *from, int len, int rt)  
    3. {  
    4.     struct sk_buff *skb;  
    5.     struct device *dev;  
    6.     struct udphdr *uh;  
    7.     unsigned char *buff;  
    8.     unsigned long saddr;  
    9.     int size, tmp;  
    10.     int ttl;  
    11.     
    12.     /*  
    13.      *  Allocate an sk_buff copy of the packet. 
    14.      */  
    15.        
    16.     ........................  
    17.   
    18.     /* 
    19.      *  Now build the IP and MAC header.  
    20.      */  
    21.        
    22.     ..........................  
    23.     /* 
    24.      *  Fill in the UDP header.  
    25.      */  
    26.        
    27.     ..............................  
    28.   
    29.     /* 
    30.      *  Copy the user data.  
    31.      */  
    32.        
    33.     memcpy_fromfs(buff, from, len);  
    34.   
    35.     /* 
    36.      *  Set up the UDP checksum.  
    37.      */  
    38.        
    39.     udp_send_check(uh, saddr, sin->sin_addr.s_addr, skb->len - tmp, sk);  
    40.   
    41.     /*  
    42.      *  Send the datagram to the interface.  
    43.      */  
    44.        
    45.     udp_statistics.UdpOutDatagrams++;  
    46.        
    47.     sk->prot->queue_xmit(sk, dev, skb, 1);  
    48.     return(len);  
    49. }  


    三、网络层

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

    1. /* 
    2.  * Queues a packet to be sent, and starts the transmitter 
    3.  * if necessary.  if free = 1 then we free the block after 
    4.  * transmit, otherwise we don't. If free==2 we not only 
    5.  * free the block but also don't assign a new ip seq number. 
    6.  * This routine also needs to put in the total length, 
    7.  * and compute the checksum 
    8.  */  
    9.   
    10. void ip_queue_xmit(struct sock *sk, struct device *dev,  
    11.           struct sk_buff *skb, int free)  
    12. {  
    13.     struct iphdr *iph;  
    14.     unsigned char *ptr;  
    15.   
    16.     /* Sanity check */  
    17.     ............  
    18.     /* 
    19.      *  Do some book-keeping in the packet for later 
    20.      */  
    21.   
    22.   
    23.     ...........  
    24.   
    25.     /* 
    26.      *  Find the IP header and set the length. This is bad 
    27.      *  but once we get the skb data handling code in the 
    28.      *  hardware will push its header sensibly and we will 
    29.      *  set skb->ip_hdr to avoid this mess and the fixed 
    30.      *  header length problem 
    31.      */  
    32.   
    33.     ..............  
    34.     /* 
    35.      *  No reassigning numbers to fragments... 
    36.      */  
    37.   
    38.     if(free!=2)  
    39.         iph->id      = htons(ip_id_count++);  
    40.     else  
    41.         free=1;  
    42.   
    43.     /* All buffers without an owner socket get freed */  
    44.     if (sk == NULL)  
    45.         free = 1;  
    46.   
    47.     skb->free = free;  
    48.   
    49.     /* 
    50.      *  Do we need to fragment. Again this is inefficient. 
    51.      *  We need to somehow lock the original buffer and use 
    52.      *  bits of it. 
    53.      */  
    54.   
    55.     ................  
    56.   
    57.     /* 
    58.      *  Add an IP checksum 
    59.      */  
    60.   
    61.     ip_send_check(iph);  
    62.   
    63.     /* 
    64.      *  Print the frame when debugging 
    65.      */  
    66.   
    67.     /* 
    68.      *  More debugging. You cannot queue a packet already on a list 
    69.      *  Spot this and moan loudly. 
    70.      */  
    71.     .......................  
    72.   
    73.     /* 
    74.      *  If a sender wishes the packet to remain unfreed 
    75.      *  we add it to his send queue. This arguably belongs 
    76.      *  in the TCP level since nobody else uses it. BUT 
    77.      *  remember IPng might change all the rules. 
    78.      */  
    79.   
    80.     ......................  
    81.     /* 
    82.      *  If the indicated interface is up and running, send the packet. 
    83.      */  
    84.        
    85.     ip_statistics.IpOutRequests++;  
    86.         .............................  
    87.     .............................  
    88.         if((dev->flags&IFF_BROADCAST) && iph->daddr==dev->pa_brdaddr && !(dev->flags&IFF_LOOPBACK))  
    89.         ip_loopback(dev,skb);  
    90.           
    91.     if (dev->flags & IFF_UP)  
    92.     {  
    93.         /* 
    94.          *  If we have an owner use its priority setting, 
    95.          *  otherwise use NORMAL 
    96.          */  
    97.   
    98.         if (sk != NULL)  
    99.         {  
    100.             dev_queue_xmit(skb, dev, sk->priority);  
    101.         }  
    102.         else  
    103.         {  
    104.             dev_queue_xmit(skb, dev, SOPRI_NORMAL);  
    105.         }  
    106.     }  
    107.     else  
    108.     {  
    109.         ip_statistics.IpOutDiscards++;  
    110.         if (free)  
    111.             kfree_skb(skb, FREE_WRITE);  
    112.     }  
    113. }  

    四、驱动层(链路层)

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

    dev->hard_start_xmit(skb, dev);

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

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

    1. /* Initialize the rest of the 8390 device structure. */  
    2. int ethdev_init(struct device *dev)  
    3. {  
    4.     if (ei_debug > 1)  
    5.         printk(version);  
    6.       
    7.     if (dev->priv == NULL) {//申请私有空间  
    8.         struct ei_device *ei_local;//8390网卡设备的结构体  
    9.           
    10.         dev->priv = kmalloc(sizeof(struct ei_device), GFP_KERNEL);//申请内核内存空间  
    11.         memset(dev->priv, 0, sizeof(struct ei_device));  
    12.         ei_local = (struct ei_device *)dev->priv;  
    13. #ifndef NO_PINGPONG  
    14.         ei_local->pingpong = 1;  
    15. #endif  
    16.     }  
    17.       
    18.     /* The open call may be overridden by the card-specific code. */  
    19.     if (dev->open == NULL)  
    20.         dev->open = &ei_open;//设备的打开函数  
    21.     /* We should have a dev->stop entry also. */  
    22.     dev->hard_start_xmit = &ei_start_xmit;//设备的发送函数,定义在8390.c中  
    23.     dev->get_stats   = get_stats;  
    24. #ifdef HAVE_MULTICAST  
    25.     dev->set_multicast_list = &set_multicast_list;  
    26. #endif  
    27.   
    28.     ether_setup(dev);  
    29.           
    30.     return 0;  
    31. }  

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


    这样就大体分析了下网络数据从应用层到物理层的数据通路,后面会详细分析。

  • 相关阅读:
    Java正则表达式, 提取双引号中间的部分
    如何快速找到未知长度单链表的中心点的值
    西格玛
    对数
    jquery显示隐藏toggle
    JavaScript:改变li前缀图片和样式
    jquery点击改变图片src源码并toggle
    jquery点击改变class并toggle
    linux下合并两个文件夹
    编译安装httpd
  • 原文地址:https://www.cnblogs.com/wangfengju/p/6173200.html
Copyright © 2020-2023  润新知