linux0.99网络模块-传输层(UDP接收)

来源:互联网 发布:java el表达式 编辑:程序博客网 时间:2024/04/29 13:46

上一篇文章《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);

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
 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             }
如果当前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       }
如果读内存超出限制,则释放(其中mem_len表示数据报首部与数据部分的长度,rmem_alloc表示读内存已分配量)
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     }

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 }

总结

总结一下udp_rcv函数的处理过程:首先从数据报中取出首部,把源地址加入到arp队列中,因为我们之后会向源主机发送数据,然后根据源IP:源端口 目的IP:目的端口找到对应的sock,如果没有发现并且目的地址不是广播地址,就向源主机发送ICMP差错报文。如果找到了对应的sock,需要进行检验和校验,通过之后,看一下是否sock正在使用,如果正在使用就把数据报放到积压队列中。否则就把数据报放入读队列并唤醒相应等待的进程。放到积压队列中的数据报也会在之后sock空闲时被加入到读队列(如果超出最大内存限制,则丢弃)。其他一些细节适当注意一下:比如对于数据报长度的修改,设备属性的设置,关联sock,源地址目的地址的交换。对于应用层来说,如果不能即使读走数据,会导致数据报被丢弃。






1 0
原创粉丝点击