Linux内核分析 - 网络[十二]:UDP模块 - 收发
副标题[/!--empirenews.page--]
内核版本:2.6.34 UDP报文接收 UDP报文的接收可以分为两个部分:协议栈收到udp报文,插入相应队列中;用户 调用recvfrom()或recv()系统调用从队列中取出报文,这里的队列就是sk->sk_receive_queue,它是报文中转的纽带,两部 分的联系如下图所示。 第一部分:协议栈如何收取udp报文的。 udp模块的注册在inet_init()中,当收到的是udp报文,会 调用udp_protocol中的handler函数udp_rcv()。 if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0) printk(KERN_CRIT "inet_init: Cannot add UDP protocoln"); udp_rcv() -> __udp4_lib_rcv() 完成udp报文接收,初始化udp的校验和,并不验证校验和的正确性。 if (udp4_csum_init(skb, uh, proto)) goto csum_error; 在udptable中以套接字的[saddr, sport, daddr, dport]查找相应的sk,在上一篇中已详细讲过” sk的查找”,这里报文的source源端口相当于源主机的端口,dest目的端口相当于本地端口。 sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable); 如果udptable中存在相应的sk,即有 socket在接收,则通过udp_queue_rcv_skb()将报文skb入队列,该函数稍后分析,总之,报文会被放到sk- >sk_receive_queue队列上,然后sock_put()减少sk的引用计算,并返回。之后的接收工作的完成将有赖于用户的操作。 if (sk != NULL) { int ret = udp_queue_rcv_skb(sk, skb); sock_put(sk); if (ret > 0) return -ret; return 0; } 当没有在udptable中找到sk时,则本机没有socket会接收它,因此要发送icmp不可达报文,在此之前,还要验证校验 和udp_lib_checksum_complete(),如果校验和错误,则直接丢弃报文;如果校验和正确,则会增加mib中的统计,并发送icmp端 口不可达报文,然后丢弃该报文。 if (udp_lib_checksum_complete(skb)) goto csum_error; UDP_INC_STATS_BH(net, UDP_MIB_NOPORTS, proto == IPPROTO_UDPLITE); icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0); kfree_skb(skb); udp_queue_rcv_skb() 报文入队列 sock_woned_by_user()判断sk- >sk_lock.owned的值,如果等于1,表示sk处于占用状态,此时不能向sk接收队列中添加skb,执行else if部分, sk_add_backlog()将skb添加到sk->sk_backlog队列上;如果等于0,表示sk没被占用,执行if部分,__udp_queue_rcv_skb() 将skb添加到sk->sk_receive_queue队列上。 bh_lock_sock(sk); if (!sock_owned_by_user(sk)) rc = __udp_queue_rcv_skb(sk, skb); else if (sk_add_backlog(sk, skb)) { bh_unlock_sock(sk); goto drop; } bh_unlock_sock(sk); 那么何时sk会被占用?何时sk->sk_backlog上的skb被处理的? 创建socket时, sys_socket() -> inet_create() -> sk_alloc() -> sock_lock_init() -> sock_lock_init_class_and_name()初 始化sk->sk_lock_owned=0。 比如当销毁socket时,udp_destroy_sock()会调用lock_sock()对sk加锁,操作完后,调用 release_sock()对sk解锁。 void udp_destroy_sock(struct sock *sk) { lock_sock(sk); udp_flush_pending_frames(sk); release_sock(sk); } 实际上,lock_sock()设置sk->sk_lock.owned=1;而release_sock()设置sk->sk_lock.owned=0,并处理sk_backlog队 列上的报文,release_sock() -> __release_sock(),对于sk_backlog队列上的每个报文,调用sk_backlog_rcv() -> sk->sk_backlog_rcv()。同样是在socket的创建中,sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv()即 __udp_queue_rcv_skb(),这个函数的作用上面已经讲过,将skb添加到sk_receive_queue,这样,所有的sk_backlog上的报文转 移到了sk_receive_queue上。简单来说,sk_backlog队列的作用就是,锁定时报文临时存放在此,解锁时,报文移到 sk_receive_queue队列。 (编辑:应用网_丽江站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |