核心版本: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 protocol\n");
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隊列。