# sendto errno -11代碼分析 errno -11在內核代碼中代表EAGAIN(再試⼀次),域套接字sendto過程中` sendto->sock_sendmsg->unix_dgram_sendmsg`,在`unix_dgram_sendmsg`中有兩處會返回 EAGAIN: 第1處 ...
sendto errno -11代碼分析
errno -11在內核代碼中代表EAGAIN(再試⼀次),域套接字sendto過程中 sendto->sock_sendmsg->unix_dgram_sendmsg
,在unix_dgram_sendmsg
中有兩處會返回 EAGAIN:
第1處:sock_alloc_send_pskb
第2處:
other!=sk&&unlikely(unix_peer(other)!=sk&&unix_recvq_full_lockless(other))
unix_peer(sk)!=other||unix_dgram_peer_wake_me(sk,other)
當以上兩個條件都滿⾜時也會返回 EAGAIN。
另外需要註意的是unix_dgram_sendmsg
中直接通過skb_queue_tail(&other->sk_receive_queue,skb)
將數據放⼊了對端的接收隊列中。
第1處
sock_alloc_send_pskb
函數中當socket發送緩衝區滿時( sk_wmem_alloc_get(sk)>=sk->sk_sndbuf
)將返回 EAGAIN。
第2處
在 Linux 內核源代碼中,unix_peer(other) != sk 表⽰另⼀個 Unix 域套接字( other )的對端套接字(peer socket)不等於當前套接字( sk )本⾝。
在 Unix 域套接字通信中,每個發起連接的進程(或線程)都必須創建兩個⽂件描述符,⼀個⽤於客⼾端(client),稱為客⼾端套接字(client socket),另⼀個⽤於伺服器端
(server),稱為伺服器套接字(server socket)。這兩個套接字通過 Unix 域⽂件系統中的某個路徑名進⾏連接(bind)。
當對等⽅成功建⽴連接後,兩個套接字中的⼀個將⾃動成為對⽅的對端套接字(peer socket)。這意味著兩個對等⽅都有⼀個指向對⽅套接字的結構體,也就是所謂的“peer
socket”。
因此,unix_peer(other) != sk 表⽰當前套接字( sk )不是另⼀個 Unix 域套接字( other )的對端套接字。如果這個條件成⽴,那麼就不能向 other 套接字發送數據,因為 other 並不是當前套接字的對端套接字,這種情況下發送數據可能會引發錯誤或者產⽣不確定的結果
unix_peer() 函數嘗試返回指向當前 Unix Domain 套接字的對端套接字的指針,如果當前套接字不是連接狀態或者沒有對端套接字則返回空指針。該函數通常⽤於判斷當前
Unix Domain 套接字是否有對端套接字,以決定是否可以進⾏數據發送。
other!=sk
, 因為other=unix_peer_get(sk)
(其實就是other=sk->peer
) ,該條件意味著sk->peer!=sk
,在域套接字中 sk代表通信的⼀端,sk->peer代表通信的另⼀端,該條件是為了避免迴圈引⽤。本⽂檔中預設 sk是客⼾端,other是服務端。unlikely(unix_peer(other)!=sk&&unix_recvq_full_lockless(other))
,⾸先unix_peer(other)!=sk
意味著sk->peer->peer!=sk
說明 sk客⼾端所指向的服務端發⽣了變化(⽐如在客⼾端發送的過程中⼜有⼀個新的客⼾端與服務端建⽴了連接),其次是unix_recvq_full_lockless(other)
如下⾯代碼所⽰,當條件滿⾜時代表著 other服務端接收隊列深度⼤於sk_max_ack_backlog
。
af_unix.c:
static inline int unix_recvq_full_lockless(conststructsock*sk)
{
return skb_queue_len_lockless(&sk->sk_receive_queue)>
READ_ONCE(sk->sk_max_ack_backlog);
}
unix_peer(sk)!=other||unix_dgram_peer_wake_me(sk,other)
,unix_peer(sk)!=other
⽤於判斷當前 Unix Domain 套接字( sk )是否為另⼀個 Unix Domain 套接字(other )的對端套接字,這⾥只能是other發⽣了變化 ;在unix_dgram_peer_wake_me
中只有other端接收隊列深度⼤於sk_max_ack_backlog
時才會return 1
。
af_unix.c:
static inline int unix_recvq_full(conststructsock*sk)
{
return skb_queue_len(&sk->sk_receive_queue) > sk->sk_max_ack_backlog;
}
static int unix_dgram_peer_wake_me(structsock*sk, structsock*other)
{
int connected;
connected = unix_dgram_peer_wake_connect(sk,other);
if(unix_recvq_full(other))
return 1;
if(connected)
unix_dgram_peer_wake_disconnect(sk,other);
return 0;
}
總結
域套接字sendto errno -11存在以下可能:
- socket發送緩衝區滿(可復現)。
- other的對端不是sk(本客⼾端)並且
unix_recvq_full_lockless
成⽴
2.1 sk(本客⼾端)的對端也不是other。
2.2 other接收隊列深度⼤於sk_max_ack_backlog
(可復現)。
條件2中 unix_recvq_full_lockless
,代表other接收隊列深度⼤於 sk_max_ack_backlog
,不過這⾥ unix_recvq_full_lockless
調⽤的是 skb_queue_len_lockless
是不加鎖的,因此這⾥存在不確定性,但⾄少內核得到的信息是other接收隊列深度⼤於 sk_max_ack_backlog
。
條件2.1和2.2成⽴的前提是條件2先成⽴。
針對條件2.1成⽴的可能性:
1)sk 與 other 建鏈,此時 sk->peer==other,other==sk->peer
。
2)new_sk(新的客⼾端)與other建鏈,此時 sk->peer==other,other->peer!=sk,other->peer==new_sk,new_sk->peer==other
。
3)new_sk⾼速發消息到other使 unix_recvq_full_lockless
條件滿⾜。
4)sk發消息進⼊unix_dgram_sendmsg
內部併到達unlikely(unix_peer(other)!=sk&&unix_recvq_full_lockless(other))
之後並且在 unix_peer(sk)!=other|| unix_dgram_peer_wake_me(sk,other)
之前的位置時,other端重新初始化了,條件 unix_peer(sk)!=other
滿⾜。
在sendto的過程中重新初始化other,⽬前沒有很好的復現⽅法。
當客⼾環境並沒有使⽤ socketpair
和 connect
,那麼sk->peer和other->peer並沒有相互引⽤,並且 other->peer==NULL
。因此other的對端不是sk(本客⼾端)並且sk(本客⼾端)的對端
也不是other,在這種情況下當other接收隊列深度⼤於 sk_max_ack_backlog
時,將返回 EAGAIN(error -11)。
事實上,unix_dgram_sendto
返回 EAGAIN時,要麼 socket發送緩衝區滿 ,要麼 other接收隊列深度⼤於sk_max_ack_backlog
,因為如果第1個if不成⽴(條件2),那麼第2個if也不會成⽴(條件2.1、條件2.2)。
調試手段
#sysctl-a|grepunix
net.unix.max_dgram_qlen=512
#sudosysctl-a|grep"net.core.wmem"
net.core.wmem_default=2097152
net.core.wmem_max=2097152
net.unix.max_dgram_qlen 代表緩衝區隊列深度(緩衝區中有多少個數據包)
net.core.wmem_max 代表緩衝區最⼤⼤小(所有數據包的總⻓度)
針對errno -11, 可適當增加
net.unix.max_dgram_qlen 的⼤小。
發送緩衝區溢出判斷
#include<sys/ioctl.h>
#include<linux/sockios.h>
long outq=0;
ioctl(sockfd,SIOCOUTQ,&outq);
errno -11 前後可根據該代碼獲取發送緩衝區的⼤小並與net.core.wmem_max
對⽐。
kprobe 監控socket 緩衝區是否溢出
kprobe監控sock_alloc_send_pskb、unix_dgram_peer_wake_me
add_kprobe_event 'r:probeE1 sock_alloc_send_pskb $retval'
set_kprobe_event_filter 'arg1 == 0' probeE1
add_kprobe_event 'r:probeE2 unix_dgram_peer_wake_me $retval'
set_kprobe_event_filter 'arg1 == 1' probeE2
add_kprobe_event 'r:probeE3 unix_dgram_sendmsg $retval'
set_kprobe_event_filter 'arg1 == 0xfffffff5' probeE3
enable_trace_event 'sock/sock_rcvqueue_full'
enable_trace_event 'sock/sock_exceed_buf_limit'
附
skb_queue_len 和 skb_queue_len_lockless區別
在 Linux 內核中,skb_queue_len 和 skb_queue_len_lockless 都是⽤於獲取 sk_buff 隊列⻓度的函數。這兩個函數的差異在於函數調⽤時是否⽀持鎖機制,具體描述如下:
skb_queue_len() 是⼀個基於鎖的隊列⻓度計算函數,當需要獲取 sk_buff 隊列⻓度時,該函數會獲取隊列的⾃旋鎖來保證隊列在計算期間不發⽣併發修改。因為該函數對隊列
實⾏鎖機制,當需要查詢 sk_buff 隊列⻓度時可能會受到鎖的競爭和等待時延等因素的影響。
skb_queue_len_lockless() 是⼀個不⽀持鎖的隊列⻓度計算函數,該函數可以在不加鎖的情況下,快速獲取 sk_buff 隊列的實際⻓度。由於該函數不需要獲取隊列的鎖,因此其
執⾏速度快,但也可能在併發寫⼊數據時因為⽆法保證數據⼀致性而產⽣錯誤的隊列⻓度計算結果。
根據內核實現,當我們希望檢查隊列的⻓度時,⼀般建議使⽤ skb_queue_len 而不是 skb_queue_len_lockless ,因為前者可以確保計算結果的準確性,並通過⾃旋鎖確保計算
過程不受併發修改的影響。而後者則主要⽤於在不需要確保 sk_buff 隊列準確性的情況下快速計算其⻓度,⽐如有些地⽅例如數據處理中可能觀察者只需要⼀個近似值,⽤
skb_queue_len_lockless() 可以顯著降低系統的開銷。
unix_socketpair
Unix 域套接字提供了⼀種可靠的進程間通信機制,其中 unix_socketpair 是其中的⼀個函數。該函數⽤於創建⼀對相互連接的套接字,這兩個套接字可以⽤於進程間的通信。
具體來說,unix_socketpair 函數創建⼀對套接字(⼜稱為 socket pair),這兩個套接字在創建時已經互相連接。這意味著,對其中⼀個套接字進⾏任何操作都會直接影響另⼀個
套接字。因此,可以使⽤這對套接字進⾏進程間通信,⽐如在兩個進程之間傳遞⽂件描述符、管道、消息等。
兩個套接字在創建時有以下特點:
套接字對是由內核創建的,不依賴於⽂件系統中存在的⽂件。
套接字對是⼀對⼀的,即⼀個套接字只能被⼀個進程所擁有,而另⼀個套接字只能被另⼀個進程所擁有。
套接字對是雙向的,即它們既可以⽤於讀也可以⽤於寫。
套接字對是數據傳輸的最小單位,因此數據交換的時候也是傳輸整塊數據,不能傳輸部分數據。
unix_socketpair 函數的調⽤⽅式如下:
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sv[2]);
其中 domain 參數⽤於指定套接字協議簇,type 參數⽤於指定套接字類型,protocol 參數⽤於指定協議類型。sv 參數是⼀個已經分配好的數組,⽤於返回 socket pair 的描述
符。調⽤成功將返回 0,否則返回 -1。
connect操作
在 Unix 域套接字中,unix_dgram_connect 函數⽤於建⽴連接並指定該連接的⽬標套接字地址。該函數主要⽤於 Datagram 套接字的客⼾端,能夠為該套接字指定⼀個預設
的⽬標地址,以便在後續的發送操作中⽆需再指定⽬標地址。
unix_dgram_connect 函數的具體作⽤如下:
建⽴連接。連接建⽴後,套接字就可以直接向指定的⽬標地址發送數據,不再需要每次都指定⽬標地址。
指定預設⽬標地址。連接建⽴後,可以使⽤ unix_dgram_sendmsg 等函數向預設⽬標地址發送數據。
接收數據。經過 unix_dgram_connect 函數連接的套接字也可以接收從指定的⽬標地址發送過來的數據。
請註意,unix_dgram_connect 函數並不是必須的,如果在套接字操作中指定了⽬標地址,則會⾃動建⽴連接。但是通過 unix_dgram_connect 函數建⽴連接可以使得發送數
據等操作更加⽅便。
unix_dgram_connect 函數的函數原型如下:
#include <sys/socket.h>
int unix_dgram_connect(int sockfd, const struct sockaddr_un* addr, socklen_t addrlen);
其中 sockfd 參數是待連接套接字的⽂件描述符,addr 參數是⽬標套接字地址,addrlen 參數是⽬標套接字地址的⻓度。調⽤成功將返回 0,否則返回 -1。
sendto
sendto時如果other不存在,則會主動通過地址和路徑去尋找other,然後通過 unix_may_send
是否可以發送,允許發送的條件是 other->peer==NULL or other->peer==sk
。
本文來自博客園,作者:StepForwards,轉載請註明原文鏈接:https://www.cnblogs.com/forwards/p/17665281.html