應用程序順序調用接收數進行接收數數據時,內核數調用過程如下
sys_recv
-> sys_recvfrom
-> sock_recvmsg
-> __sock_recvmsg
-> sock->ops->recvmsg => sock_common_recvmsg
-> sk->sk_prot->recvmsg => tcp_recvmsg
最后協議棧通過調 使用tcp_recvmsg 從接收隊列中獲取數據采集到用戶文件夾中。
tcp_recvmsg
//把數據從接收隊列中復制到用戶空間中
int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
size_t len, int nonblock, int flags, int *addr_len)
{
struct tcp_sock *tp = tcp_sk(sk);
int copied = 0;
u32 peek_seq;
u32 *seq;
unsigned long used;
int err;
int target; /* Read at least this many bytes */
long timeo;
struct task_struct *user_recv = NULL;
int copied_early = 0;
//先對傳輸層上鎖,以免在讀的過程中,軟中斷操作傳輸層,對數據不同步造成后果
lock_sock(sk);
TCP_CHECK_TIMER(sk);
//初始化錯誤碼
err = -ENOTCONN;
//TCP_LISTEN狀態 不允許讀
if (sk->sk_state == TCP_LISTEN)
goto out;
// 獲取阻塞超時時間,若非阻塞讀取,超時時間為0
timeo = sock_rcvtimeo(sk, nonblock);
/* Urgent data needs to be handled specially. */
///若讀取外帶數據,則跳轉處理
if (flags & MSG_OOB)
goto recv_urg;
/*判斷是從緩沖區讀取數據還是只是查看數據: 若是讀取數據到用戶空間,會更新copied_seq,
而只是查看數據,不需更新copied_seq,所以在這里先判斷是讀緩沖區數據還是只是查看數據*/
seq = &tp->copied_seq;
if (flags & MSG_PEEK) {
peek_seq = tp->copied_seq;
seq = &peek_seq;
}
/* 根據是否設置MSG_WAITALL來確定本次調用需要接收數據的長度,
若設置該標志,則讀取數據的長度為用戶調用時的輸入參數len */
target = sock_rcvlowat(sk, flags & MSG_WAITALL, len);
do {
struct sk_buff *skb;
u32 offset;
/* Are we at urgent data? Stop if we have read anything or have SIGURG pending.
通過urg_data 和 urg_seq 來檢測當前是否讀取到外帶數據。
*/
if (tp->urg_data && tp->urg_seq == *seq) {
//若在讀取到外帶數據之前已經讀取了部分數據,則終止本次正常數據的讀取。
if (copied)
break;
//若用戶進程有信號待處理,則也終止本次的讀取
if (signal_pending(current)) {
copied = timeo ? sock_intr_errno(timeo) : -EAGAIN;
break;
}
}
/* Next get a buffer. */
//獲取下一個待讀取的段
skb = skb_peek(&sk->sk_receive_queue);
do {
//若隊列為空,這只能接著處理prequeue或后備隊列
if (!skb)
break;
/* Now that we have two receive queues this
* shouldn't happen.
*/
//若接收隊列中段序號大,說明也獲取不到待讀取的段,只能接著處理prequeue或后備隊列
if (before(*seq, TCP_SKB_CB(skb)->seq)) {
printk(KERN_INFO "recvmsg bug: copied %X "
"seq %X\\n", *seq, TCP_SKB_CB(skb)->seq);
break;
}
//計算該段讀取數據的偏移位置,該偏移位置必須在該段的數據長度范圍內才有效
offset = *seq - TCP_SKB_CB(skb)->seq;
///SYN標志占用了一個序號,因此若存在SYN,則調整偏移
if (skb->h.th->syn)
offset--;
//偏移位置必須在該段的數據長度范圍內才有效
if (offset < skb->len)
goto found_ok_skb;
//若存在fin標志,跳轉處理
if (skb->h.th->fin)
goto found_fin_ok;
BUG_TRAP(flags & MSG_PEEK);
skb = skb->next;
} while (skb != (struct sk_buff *)&sk->sk_receive_queue);
/* Well, if we have backlog, try to process it now yet. */
//只有在讀取完數據后,才能在后備隊列不為空的情況下,去處理接收到后備隊列中的tcp段,否則終止本次讀取
if (copied >= target && !sk->sk_backlog.tail)
break;
/*接收隊列中可讀的段已讀完,在處理prequeue或后備隊列之前需要檢測是否有導致返回的事件、狀態等*/
if (copied) {
if (sk->sk_err || //有錯誤發送
sk->sk_state == TCP_CLOSE ||
(sk->sk_shutdown & RCV_SHUTDOWN) || //shutdown后不允許接收數據
!timeo || //非阻塞
signal_pending(current) || //收到信號
(flags & MSG_PEEK)) //只是查看數據
break; //上面檢測條件只要有成立立即退出本次讀取
} else {
//檢測tcp會話是否即將終結
if (sock_flag(sk, SOCK_DONE))
break;
///有錯誤發生,返回錯誤碼
if (sk->sk_err) {
copied = sock_error(sk);
break;
}
if (sk->sk_shutdown & RCV_SHUTDOWN)
break;
//tcp狀態處于close,而套接口不在終結狀態,則進程可能是在讀一個沒有建立起連接的套接口,則返回ENOTCONN
if (sk->sk_state == TCP_CLOSE) {
if (!sock_flag(sk, SOCK_DONE)) {
/* This occurs when user tries to read
* from never connected socket.
*/
copied = -ENOTCONN;
break;
}
break;
}
//未讀到數據,且是非阻塞讀,返回EAGAIN
if (!timeo) {
copied = -EAGAIN;
break;
}
//檢測是否收到數據,同時獲取相應的錯誤碼
if (signal_pending(current)) {
copied = sock_intr_errno(timeo);
break;
}
}
//檢測是否有確認需要立即發送
tcp_cleanup_rbuf(sk, copied);
//在未啟用sysctl_tcp_low_latency情況下,檢查tcp_low_latency,默認其為0,表示使用prequeue隊列
if (!sysctl_tcp_low_latency && tp->ucopy.task == user_recv) {
/* Install new reader */
/*若是本次讀取的第一此檢測處理prequeue隊列,則需要設置正在讀取的進程描述符、緩存地址信息。這樣當
讀取進程進入睡眠后,ESTABLISHED狀態的接收處理就可能直接把數據復制到用戶空間*/
if (!user_recv && !(flags & (MSG_TRUNC | MSG_PEEK))) {
user_recv = current;
tp->ucopy.task = user_recv;
tp->ucopy.iov = msg->msg_iov;
}
//更新當前可以使用的用戶緩存大小
tp->ucopy.len = len;
BUG_TRAP(tp->copied_seq == tp->rcv_nxt ||
(flags & (MSG_PEEK | MSG_TRUNC)));
//若prequeue不為空,跳轉處理prequeue隊列
if (!skb_queue_empty(&tp->ucopy.prequeue))
goto do_prequeue;
/* __ Set realtime policy in scheduler __ */
}
//若數據讀取完,調用release_sock 解鎖傳輸控制塊,主要用來處理后備隊列
if (copied >= target) {
/* Do not sleep, just process backlog. */
release_sock(sk);
//鎖定傳輸控制塊,在調用lock_sock時進程可能會出現睡眠
lock_sock(sk);
} else
/*若數據未讀取,且是阻塞讀取,則進入睡眠等待接收數據。在這種情況下,tcp_v4_do_rcv處理
tcp段時可能會把數據直接復制到用戶空間*/
sk_wait_data(sk, &timeo);
if (user_recv) {
int chunk;
/* __ Restore normal policy in scheduler __ */
//更新剩余的用戶空間長度和已復制到用戶空間的數據長度
if ((chunk = len - tp->ucopy.len) != 0) {
NET_ADD_STATS_USER(LINUX_MIB_TCPDIRECTCOPYFROMBACKLOG, chunk);
len -= chunk;
copied += chunk;
}
/*若接收到接收隊列中的數據已經全部復制到用戶進程空間,但prequeue隊列不為空,則需繼續處理prequeue隊列,
并更新剩余的用戶空間長度和已復制到用戶空間的數據長度*/
if (tp->rcv_nxt == tp->copied_seq &&
!skb_queue_empty(&tp->ucopy.prequeue)) {
do_prequeue:
tcp_prequeue_process(sk);
if ((chunk = len - tp->ucopy.len) != 0) {
NET_ADD_STATS_USER(LINUX_MIB_TCPDIRECTCOPYFROMPREQUEUE, chunk);
len -= chunk;
copied += chunk;
}
}
}
//處理完prequeue隊列后,若有更新copied_seq,且只是查看數據,則需要更新peek_seq
if ((flags & MSG_PEEK) && peek_seq != tp->copied_seq) {
if (net_ratelimit())
printk(KERN_DEBUG "TCP(%s:%d): Application bug, race in MSG_PEEK.\\n",
current->comm, current->pid);
peek_seq = tp->copied_seq;
}
//繼續獲取下一個待讀取的段作處理
continue;
found_ok_skb:
/* Ok so how much can we use? */
/*獲取該可讀取段的數據長度,在前面的處理中已由tcp序號得到本次讀取數據在該段中的偏移offset*/
used = skb->len - offset;
if (len < used)
used = len;
/* Do we have urgent data here? */
//若段內包含帶外數據,則獲取帶外數據在該段中的偏移
if (tp->urg_data) {
u32 urg_offset = tp->urg_seq - *seq;
if (urg_offset < used) {
//若偏移為0,說明目前需要的數據正是帶外數據,且帶外數據不允許進入正常的數據流
if (!urg_offset) {
if (!sock_flag(sk, SOCK_URGINLINE)) {
++*seq;
offset++;
used--;
if (!used)
goto skip_copy;
}
} else
//若偏移不為0,則需要調整本次讀取的正常數據長度直到讀到帶外數據為止
used = urg_offset;
}
}
//處理讀取數據的情況
if (!(flags & MSG_TRUNC)) {
{
//將數據復制到用戶空間
err = skb_copy_datagram_iovec(skb, offset,
msg->msg_iov, used);
if (err) {
/* Exception. Bailout! */
if (!copied)
copied = -EFAULT;
break;
}
}
}
*seq += used; //調整已讀取數據的序號
copied += used;//調整已讀取數據的長度
len -= used;//調整剩余的可用空間緩存大小
//調整合理的tcp接收緩沖區大小
tcp_rcv_space_adjust(sk);
skip_copy:
//若對帶外數據處理完畢,則將標志清零,
if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) {
tp->urg_data = 0;
//設置首部標志,下一個接收段又可以通過首部預測執行快慢速路徑
tcp_fast_path_check(sk, tp);
}
//若該段還有數據未讀取(如帶外數據),則是能繼續處理該段,而不能把該段從接收隊列中刪除
if (used + offset < skb->len)
continue;
if (skb->h.th->fin)
goto found_fin_ok;
if (!(flags & MSG_PEEK)) {
sk_eat_skb(sk, skb, copied_early);
copied_early = 0;
}
//繼續處理后續的段
continue;
found_fin_ok:
/* Process the FIN. */
//由于fin標志占用一個序號,因此當前讀取的序號需遞增
++*seq;
if (!(flags & MSG_PEEK)) {
sk_eat_skb(sk, skb, copied_early);
copied_early = 0;
}
//接收到fin標志,無需繼續處理后續的段
break;
} while (len > 0);
if (user_recv) {
//不空,處理prequeue
if (!skb_queue_empty(&tp->ucopy.prequeue)) {
int chunk;
tp->ucopy.len = copied > 0 ? len : 0;
tcp_prequeue_process(sk);
//若在處理prequeue隊列過程中又有一部分數據復制到用戶空間,則調整剩余的可用空間緩存大小和已讀數據的序號
if (copied > 0 && (chunk = len - tp->ucopy.len) != 0) {
NET_ADD_STATS_USER(LINUX_MIB_TCPDIRECTCOPYFROMPREQUEUE, chunk);
len -= chunk;
copied += chunk;
}
}
/*清零,表示用戶當前沒有讀取數據。這樣當處理prequeue隊列時不會將數據復制到用戶空間,
因為只有在未啟用tcp_low_latency下,用戶主動讀取時,才有機會將數據直接復制到用戶空間*/
tp->ucopy.task = NULL;
tp->ucopy.len = 0;
}
/*在完成讀取數據后,需再次檢測是否有必要立即發送ack,并根據情況確定是否發送ack段*/
tcp_cleanup_rbuf(sk, copied);
TCP_CHECK_TIMER(sk);
//返回前解鎖傳輸控制塊
release_sock(sk);
//返回已讀取的字節數
return copied;
/*若在讀取過程中發生了錯誤,則會跳轉到此,解鎖傳輸層后返回錯誤碼*/
out:
TCP_CHECK_TIMER(sk);
release_sock(sk);
return err;
/*若是接收帶外數據,則調用tcp_recv_urg接收*/
recv_urg:
err = tcp_recv_urg(sk, timeo, msg, len, flags, addr_len);
goto out;
}
以上調使用排除帶外數據只分析正常的數據的話,處理過程如下:
1、根據尚未從內核空間復制到用戶空間的最前面一個字節的序號,找到待貝的數據塊。
2、將數據從數據塊中選擇貝到用戶空間。
3、調整合理的TCP接收綁定沖區大小
4、跳到第一步循環處理,直達到滿足用戶讀取數量的條件。
審核編輯:劉清
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。
舉報投訴
-
TCP
+關注
關注
8文章
1378瀏覽量
79303 -
LINUX內核
+關注
關注
1文章
316瀏覽量
21743 -
ADD
+關注
關注
1文章
20瀏覽量
9462
發布評論請先 登錄
相關推薦
C語言函數調用過程中的內存變化解析
相信很多編程新手村的同學們都會有一個疑問:C 語言如何調用函數的呢?局部變量的作用域為什么僅限于函數內?這個調用不是指C 語言上的函數調用的語法,而是在內存的視角下,函數的調用過程。本
Linux內核中系統調用詳解
Linux內核中設置了一組用于實現各種系統功能的子程序,稱為系統調用。用戶可以通過系統調用命令在自己的應用程序中調用它們。從某種角度來看,系
發表于 08-23 10:37
?820次閱讀
![<b class='flag-5'>Linux</b><b class='flag-5'>內核</b>中系統<b class='flag-5'>調用</b>詳解](https://file1.elecfans.com/web2/M00/94/68/wKgZomTlcWiAEOJgAAAQ5XaBP0g428.jpg)
Linux內核啟動過程和Bootloader(總述)
,所以一般的 Bootloader 都會在執行過程中初始化一個串口做為調試端口(3)檢測處理器類型 Bootloader在調用 Linux內核前必須檢測系統的處理器類型,并將其保存到某
發表于 08-18 17:35
ARM linux系統調用的實現原理
大家都知道linux的應用程序要想訪問內核必須使用系統調用從而實現從usr模式轉到svc模式。下面咱們看看它的實現過程。
發表于 05-30 11:24
?2246次閱讀
Linux內核系統調用擴展研究
系統凋用是操作系統內核提供給用戶使用內核服務的接口。LinuX操作系統由于其自由開放性,用戶可在原有基礎上,添加新的系統調用,以便提供更多的服務。基于Linttx2.4
發表于 07-25 16:09
?40次下載
![<b class='flag-5'>Linux</b><b class='flag-5'>內核</b>系統<b class='flag-5'>調用</b>擴展研究](https://file.elecfans.com/web2/M00/49/0C/pYYBAGKhtDWAel1PAAAOzDgc90o195.jpg)
編譯Linux2.6內核并添加一個系統調用
本文以實例來詳細描述了從準備一直到使用新內核的Linux2.6 內核編譯過程,然后介紹了添加系統調用的實現步驟,最后給實驗結果。
發表于 12-01 15:54
?46次下載
lattice DDR3 IP核的生成及調用過程
本文以一個案例的形式來介紹lattice DDR3 IP核的生成及調用過程,同時介紹各個接口信號的功能作用
發表于 03-16 14:14
?2212次閱讀
![lattice DDR3 IP核的生成及<b class='flag-5'>調用過程</b>](https://file.elecfans.com//web2/M00/36/39/pYYBAGIxgDuAc1OSAAPAuDo5oK4924.png)
如何區分xenomai、linux系統調用/服務
對于同一個POSIX接口應用程序,可能既需要xenomai內核提供服務(xenomai 系統調用),又需要調用linux內核提供服務(
系統調用:用戶棧與內核棧的切換(上)
sysenter / sysexit 再到 syscall / sysret 實現方式的轉變,關于具體的演化和區別、系統調用的其他細節等將在以后的系統調用專欄里分析。本文從系統調用最原始的int 0x80開始分析用戶棧與
![系統<b class='flag-5'>調用</b>:用戶棧與<b class='flag-5'>內核</b>棧的切換(上)](https://file1.elecfans.com/web2/M00/8E/6D/wKgZomTHKQeARCFYAADCLKwpNvQ383.jpg)
評論