715 /* At this point we should print the thing out. */
716 PRINTK ("<< \n");
717 print_sk (sk);
719 /* now add it to the data chain and wake things up. */
720 if (sk->rqueue == NULL)
721 {
722 sk->rqueue = skb;
723 skb->next = skb;
724 skb->prev = skb;
725 }
726 else
727 {
728 skb->next = sk->rqueue;
729 skb->prev = sk->rqueue->prev;
730 skb->prev->next = skb;
731 skb->next->prev = skb;
732 }
将skb加入读队列链表中(可以看到是加入到了首部)
那么问题来了,这里sock的rqueue与 rmem_alloc之间是什么关系?应用程序又是从何处读取数据的?
rqueue是用来链接数据报的,rmem_alloc用来记录读队列的大小,目的是防止过多的数据报导致内存溢出。接收数据报之后,就会唤醒等待的进程,从而可以读取数据。
734 skb->len = len - sizeof (*uh);
更新长度,这里是数据部分的长度
736 if (!sk->dead)
737 wake_up (sk->sleep);
唤醒等待进程
739 release_sock (sk);
740 return (0);
741 }
1809 sti();
1810 PRINTK ("sk->back_log = %X\n",sk->back_log);
1811 if (sk->prot->rcv)
1812 sk->prot->rcv(skb, skb->dev, sk->opt,
1813 skb->saddr, skb->len, skb->daddr, 1,
1814 /* only used for/by raw sockets. */
1815 (struct ip_protocol *)sk->pair);
这里inuse=1,继续调用rcv方法岂不是还加入积压队列?
总结
总结一下udp_rcv函数的处理过程:首先从数据报中取出首部,把源地址加入到arp队列中,因为我们之后会向源主机发送数据,然后根据源IP:源端口 目的IP:目的端口找到对应的sock,如果没有发现并且目的地址不是广播地址,就向源主机发送ICMP差错报文。如果找到了对应的sock,需要进行检验和校验,通过之后,看一下是否sock正在使用,如果正在使用就把数据报放到积压队列中。否则就把数据报放入读队列并唤醒相应等待的进程。放到积压队列中的数据报也会在之后sock空闲时被加入到读队列(如果超出最大内存限制,则丢弃)。其他一些细节适当注意一下:比如对于数据报长度的修改,设备属性的设置,关联sock,源地址目的地址的交换。对于应用层来说,如果不能即使读走数据,会导致数据报被丢弃。
上一篇文章《linux0.99网络模块-网络层(接收)》中我们提到过,注册到IP层的协议有ICMP,TCP,UDP。本文就来分析UDP处理数据报的过程。
我们记得上一篇中网络层通过调用下面的函数来把数据报传递给UDP。
775 ipprot->handler (skb2, dev, &opt, iph->daddr,
776 net16(iph->tot_len) - iph->ihl*4,
777 iph->saddr, 0, ipprot);
776 net16(iph->tot_len) - iph->ihl*4,
777 iph->saddr, 0, ipprot);
net/tcp/protocols.c
66 static struct ip_protocol udp_protocol =
67 {
68 udp_rcv,
69 udp_err,
70 &tcp_protocol,
71 IPPROTO_UDP,
72 0, /* copy */
73 NULL
67 {
68 udp_rcv,
69 udp_err,
70 &tcp_protocol,
71 IPPROTO_UDP,
72 0, /* copy */
73 NULL
74 };
可以看到UDP的handler为udp_rcv。
我们来看一下:
net/tcp/udp.c
632 int
633 udp_rcv(struct sk_buff *skb, struct device *dev, struct options *opt,
634 unsigned long daddr, unsigned short len,
635 unsigned long saddr, int redo, struct ip_protocol *protocol)
636 {
637 /* all we need to do is get the socket, and then do a checksum. */
638 struct proto *prot=&udp_prot;
639 volatile struct sock *sk;
640 struct udp_header *uh;
642 uh = (struct udp_header *) skb->h.uh;
644 if (dev->add_arp) dev->add_arp (saddr, skb, dev);
添加到arp队列中
646 sk = get_sock (prot, net16(uh->dest), saddr, uh->source, daddr);
根据目的IP地址和端口号寻找对应的sock,具体在《linux0.99网络模块-传输层(TCP接收)》中有分析
648 /* if we don't know about the socket, forget about it. */
649 if (sk == NULL)
650 {
651 if ((daddr & 0xff000000 != 0) &&
652 (daddr & 0xff000000 != 0xff000000))
653 {
654 icmp_reply (skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, dev);
655 }
656 skb->sk = NULL;
657 kfree_skb (skb, 0);
658 return (0);
659 }
如果没有找到对应的sock说明目的主机不可达,端口不可达,这时只要不是广播地址,就给源主机发送ICMP差错报文
662 if (!redo)
663 {
664 if (uh->check && udp_check (uh, len, saddr, daddr))
665 {
666 PRINTK ("bad udp checksum\n");
667 skb->sk = NULL;
668 kfree_skb (skb, 0);
669 return (0);
670 }
校验和检查
672 skb->sk = sk;
673 skb->dev = dev;
674 skb->len = len;
676 /* these are supposed to be switched. */
677 skb->daddr = saddr;
678 skb->saddr = daddr;
设置skb字段
680 /* Now deal with the in use. */
681 cli();
682 if (sk->inuse)
683 {
684 if (sk->back_log == NULL)
685 {
685 {
686 sk->back_log = skb;
687 skb->next = skb;
688 skb->prev = skb;
689 }
690 else
691 {
692 skb->next = sk->back_log;
693 skb->prev = sk->back_log->prev;
694 skb->prev->next = skb;
695 skb->next->prev = skb;
696 }
633 udp_rcv(struct sk_buff *skb, struct device *dev, struct options *opt,
634 unsigned long daddr, unsigned short len,
635 unsigned long saddr, int redo, struct ip_protocol *protocol)
636 {
637 /* all we need to do is get the socket, and then do a checksum. */
638 struct proto *prot=&udp_prot;
639 volatile struct sock *sk;
640 struct udp_header *uh;
642 uh = (struct udp_header *) skb->h.uh;
644 if (dev->add_arp) dev->add_arp (saddr, skb, dev);
添加到arp队列中
646 sk = get_sock (prot, net16(uh->dest), saddr, uh->source, daddr);
根据目的IP地址和端口号寻找对应的sock,具体在《linux0.99网络模块-传输层(TCP接收)》中有分析
648 /* if we don't know about the socket, forget about it. */
649 if (sk == NULL)
650 {
651 if ((daddr & 0xff000000 != 0) &&
652 (daddr & 0xff000000 != 0xff000000))
653 {
654 icmp_reply (skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, dev);
655 }
656 skb->sk = NULL;
657 kfree_skb (skb, 0);
658 return (0);
659 }
如果没有找到对应的sock说明目的主机不可达,端口不可达,这时只要不是广播地址,就给源主机发送ICMP差错报文
662 if (!redo)
663 {
664 if (uh->check && udp_check (uh, len, saddr, daddr))
665 {
666 PRINTK ("bad udp checksum\n");
667 skb->sk = NULL;
668 kfree_skb (skb, 0);
669 return (0);
670 }
校验和检查
672 skb->sk = sk;
673 skb->dev = dev;
674 skb->len = len;
676 /* these are supposed to be switched. */
677 skb->daddr = saddr;
678 skb->saddr = daddr;
设置skb字段
680 /* Now deal with the in use. */
681 cli();
682 if (sk->inuse)
683 {
684 if (sk->back_log == NULL)
685 {
685 {
686 sk->back_log = skb;
687 skb->next = skb;
688 skb->prev = skb;
689 }
690 else
691 {
692 skb->next = sk->back_log;
693 skb->prev = sk->back_log->prev;
694 skb->prev->next = skb;
695 skb->next->prev = skb;
696 }
如果当前sock正在使用就将skb加入到积压队列中(队列首部)
697 sti();
698 return (0);
699 }
700 sk->inuse = 1;
701 sti();
702 }
704 /* charge it too the socket. */
705 if (sk->rmem_alloc + skb->mem_len >= SK_RMEM_MAX)
706 {
707 skb->sk = NULL;
708 kfree_skb (skb, 0);
709 release_sock (sk);
710 return (0);
711 }
697 sti();
698 return (0);
699 }
700 sk->inuse = 1;
701 sti();
702 }
704 /* charge it too the socket. */
705 if (sk->rmem_alloc + skb->mem_len >= SK_RMEM_MAX)
706 {
707 skb->sk = NULL;
708 kfree_skb (skb, 0);
709 release_sock (sk);
710 return (0);
711 }
如果读内存超出限制,则释放(其中mem_len表示数据报首部与数据部分的长度,rmem_alloc表示读内存已分配量)
713 sk->rmem_alloc += skb->mem_len;
713 sk->rmem_alloc += skb->mem_len;
更新读内存使用量
715 /* At this point we should print the thing out. */
716 PRINTK ("<< \n");
717 print_sk (sk);
719 /* now add it to the data chain and wake things up. */
720 if (sk->rqueue == NULL)
721 {
722 sk->rqueue = skb;
723 skb->next = skb;
724 skb->prev = skb;
725 }
726 else
727 {
728 skb->next = sk->rqueue;
729 skb->prev = sk->rqueue->prev;
730 skb->prev->next = skb;
731 skb->next->prev = skb;
732 }
将skb加入读队列链表中(可以看到是加入到了首部)
那么问题来了,这里sock的rqueue与 rmem_alloc之间是什么关系?应用程序又是从何处读取数据的?
rqueue是用来链接数据报的,rmem_alloc用来记录读队列的大小,目的是防止过多的数据报导致内存溢出。接收数据报之后,就会唤醒等待的进程,从而可以读取数据。
734 skb->len = len - sizeof (*uh);
更新长度,这里是数据部分的长度
736 if (!sk->dead)
737 wake_up (sk->sleep);
唤醒等待进程
739 release_sock (sk);
740 return (0);
741 }
739行涉及到一个方法release_sock我们再来看一下:
1773 void release_sock (volatile struct sock *sk)
1774 {
1775 if (!sk)
1776 {
1777 printk ("sock.c: release_sock sk == NULL\n");
1778 return;
1779 }
1781 if (!sk->prot)
1782 {
1783 printk ("sock.c: release_sock sk->prot == NULL\n");
1784 return;
1785 }
1787 if (sk->blog) return;
1788 /* see if we have any packets built up. */
1790 cli();
1791 sk->inuse = 1;
1792 while (sk->back_log != NULL)
1793 {
1794 struct sk_buff *skb;
1795 sk->blog = 1;
1796 skb = sk->back_log;
1797 PRINTK ("release_sock: skb = %X:\n",skb);
1798 print_skb(skb);
1799 if (skb->next != skb)
1800 {
1801 sk->back_log = skb->next;
1802 skb->prev->next = skb->next;
1803 skb->next->prev = skb->prev;
1804 }
1805 else
1806 {
1807 sk->back_log = NULL;
1808 }
1774 {
1775 if (!sk)
1776 {
1777 printk ("sock.c: release_sock sk == NULL\n");
1778 return;
1779 }
1781 if (!sk->prot)
1782 {
1783 printk ("sock.c: release_sock sk->prot == NULL\n");
1784 return;
1785 }
1787 if (sk->blog) return;
1788 /* see if we have any packets built up. */
1790 cli();
1791 sk->inuse = 1;
1792 while (sk->back_log != NULL)
1793 {
1794 struct sk_buff *skb;
1795 sk->blog = 1;
1796 skb = sk->back_log;
1797 PRINTK ("release_sock: skb = %X:\n",skb);
1798 print_skb(skb);
1799 if (skb->next != skb)
1800 {
1801 sk->back_log = skb->next;
1802 skb->prev->next = skb->next;
1803 skb->next->prev = skb->prev;
1804 }
1805 else
1806 {
1807 sk->back_log = NULL;
1808 }
1809 sti();
1810 PRINTK ("sk->back_log = %X\n",sk->back_log);
1811 if (sk->prot->rcv)
1812 sk->prot->rcv(skb, skb->dev, sk->opt,
1813 skb->saddr, skb->len, skb->daddr, 1,
1814 /* only used for/by raw sockets. */
1815 (struct ip_protocol *)sk->pair);
这里inuse=1,继续调用rcv方法岂不是还加入积压队列?
其实不会,可以看到这里的参数 redo == 1,所以上面的函数682-702行不会执行。它会被加入到读队列中。
1816 cli();
1817 } // while
1818 sk->blog = 0;
1819 sk->inuse = 0;
1820 sti();
1821 if (sk->dead && sk->state == TCP_CLOSE)
1822 {
1823 /* should be about 2 rtt's */
1824 sk->time_wait.len = min (sk->rtt * 2, TCP_DONE_TIME);
1825 sk->timeout = TIME_DONE;
1826 reset_timer ((struct timer *)&sk->time_wait);
1827 }
1828 }
1817 } // while
1818 sk->blog = 0;
1819 sk->inuse = 0;
1820 sti();
1821 if (sk->dead && sk->state == TCP_CLOSE)
1822 {
1823 /* should be about 2 rtt's */
1824 sk->time_wait.len = min (sk->rtt * 2, TCP_DONE_TIME);
1825 sk->timeout = TIME_DONE;
1826 reset_timer ((struct timer *)&sk->time_wait);
1827 }
1828 }
总结
总结一下udp_rcv函数的处理过程:首先从数据报中取出首部,把源地址加入到arp队列中,因为我们之后会向源主机发送数据,然后根据源IP:源端口 目的IP:目的端口找到对应的sock,如果没有发现并且目的地址不是广播地址,就向源主机发送ICMP差错报文。如果找到了对应的sock,需要进行检验和校验,通过之后,看一下是否sock正在使用,如果正在使用就把数据报放到积压队列中。否则就把数据报放入读队列并唤醒相应等待的进程。放到积压队列中的数据报也会在之后sock空闲时被加入到读队列(如果超出最大内存限制,则丢弃)。其他一些细节适当注意一下:比如对于数据报长度的修改,设备属性的设置,关联sock,源地址目的地址的交换。对于应用层来说,如果不能即使读走数据,会导致数据报被丢弃。