應(yīng)用程序順序調(diào)用接收數(shù)進(jìn)行接收數(shù)數(shù)據(jù)時(shí),內(nèi)核數(shù)調(diào)用過(guò)程如下
sys_recv
-> sys_recvfrom
-> sock_recvmsg
-> __sock_recvmsg
-> sock->ops->recvmsg => sock_common_recvmsg
-> sk->sk_prot->recvmsg => tcp_recvmsg
最后協(xié)議棧通過(guò)調(diào) 使用tcp_recvmsg 從接收隊(duì)列中獲取數(shù)據(jù)采集到用戶文件夾中。
tcp_recvmsg
//把數(shù)據(jù)從接收隊(duì)列中復(fù)制到用戶空間中
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;
//先對(duì)傳輸層上鎖,以免在讀的過(guò)程中,軟中斷操作傳輸層,對(duì)數(shù)據(jù)不同步造成后果
lock_sock(sk);
TCP_CHECK_TIMER(sk);
//初始化錯(cuò)誤碼
err = -ENOTCONN;
//TCP_LISTEN狀態(tài) 不允許讀
if (sk->sk_state == TCP_LISTEN)
goto out;
// 獲取阻塞超時(shí)時(shí)間,若非阻塞讀取,超時(shí)時(shí)間為0
timeo = sock_rcvtimeo(sk, nonblock);
/* Urgent data needs to be handled specially. */
///若讀取外帶數(shù)據(jù),則跳轉(zhuǎn)處理
if (flags & MSG_OOB)
goto recv_urg;
/*判斷是從緩沖區(qū)讀取數(shù)據(jù)還是只是查看數(shù)據(jù): 若是讀取數(shù)據(jù)到用戶空間,會(huì)更新copied_seq,
而只是查看數(shù)據(jù),不需更新copied_seq,所以在這里先判斷是讀緩沖區(qū)數(shù)據(jù)還是只是查看數(shù)據(jù)*/
seq = &tp->copied_seq;
if (flags & MSG_PEEK) {
peek_seq = tp->copied_seq;
seq = &peek_seq;
}
/* 根據(jù)是否設(shè)置MSG_WAITALL來(lái)確定本次調(diào)用需要接收數(shù)據(jù)的長(zhǎng)度,
若設(shè)置該標(biāo)志,則讀取數(shù)據(jù)的長(zhǎng)度為用戶調(diào)用時(shí)的輸入參數(shù)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.
通過(guò)urg_data 和 urg_seq 來(lái)檢測(cè)當(dāng)前是否讀取到外帶數(shù)據(jù)。
*/
if (tp->urg_data && tp->urg_seq == *seq) {
//若在讀取到外帶數(shù)據(jù)之前已經(jīng)讀取了部分?jǐn)?shù)據(jù),則終止本次正常數(shù)據(jù)的讀取。
if (copied)
break;
//若用戶進(jìn)程有信號(hào)待處理,則也終止本次的讀取
if (signal_pending(current)) {
copied = timeo ? sock_intr_errno(timeo) : -EAGAIN;
break;
}
}
/* Next get a buffer. */
//獲取下一個(gè)待讀取的段
skb = skb_peek(&sk->sk_receive_queue);
do {
//若隊(duì)列為空,這只能接著處理prequeue或后備隊(duì)列
if (!skb)
break;
/* Now that we have two receive queues this
* shouldn't happen.
*/
//若接收隊(duì)列中段序號(hào)大,說(shuō)明也獲取不到待讀取的段,只能接著處理prequeue或后備隊(duì)列
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;
}
//計(jì)算該段讀取數(shù)據(jù)的偏移位置,該偏移位置必須在該段的數(shù)據(jù)長(zhǎng)度范圍內(nèi)才有效
offset = *seq - TCP_SKB_CB(skb)->seq;
///SYN標(biāo)志占用了一個(gè)序號(hào),因此若存在SYN,則調(diào)整偏移
if (skb->h.th->syn)
offset--;
//偏移位置必須在該段的數(shù)據(jù)長(zhǎng)度范圍內(nèi)才有效
if (offset < skb->len)
goto found_ok_skb;
//若存在fin標(biāo)志,跳轉(zhuǎn)處理
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. */
//只有在讀取完數(shù)據(jù)后,才能在后備隊(duì)列不為空的情況下,去處理接收到后備隊(duì)列中的tcp段,否則終止本次讀取
if (copied >= target && !sk->sk_backlog.tail)
break;
/*接收隊(duì)列中可讀的段已讀完,在處理prequeue或后備隊(duì)列之前需要檢測(cè)是否有導(dǎo)致返回的事件、狀態(tài)等*/
if (copied) {
if (sk->sk_err || //有錯(cuò)誤發(fā)送
sk->sk_state == TCP_CLOSE ||
(sk->sk_shutdown & RCV_SHUTDOWN) || //shutdown后不允許接收數(shù)據(jù)
!timeo || //非阻塞
signal_pending(current) || //收到信號(hào)
(flags & MSG_PEEK)) //只是查看數(shù)據(jù)
break; //上面檢測(cè)條件只要有成立立即退出本次讀取
} else {
//檢測(cè)tcp會(huì)話是否即將終結(jié)
if (sock_flag(sk, SOCK_DONE))
break;
///有錯(cuò)誤發(fā)生,返回錯(cuò)誤碼
if (sk->sk_err) {
copied = sock_error(sk);
break;
}
if (sk->sk_shutdown & RCV_SHUTDOWN)
break;
//tcp狀態(tài)處于close,而套接口不在終結(jié)狀態(tài),則進(jìn)程可能是在讀一個(gè)沒(méi)有建立起連接的套接口,則返回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;
}
//未讀到數(shù)據(jù),且是非阻塞讀,返回EAGAIN
if (!timeo) {
copied = -EAGAIN;
break;
}
//檢測(cè)是否收到數(shù)據(jù),同時(shí)獲取相應(yīng)的錯(cuò)誤碼
if (signal_pending(current)) {
copied = sock_intr_errno(timeo);
break;
}
}
//檢測(cè)是否有確認(rèn)需要立即發(fā)送
tcp_cleanup_rbuf(sk, copied);
//在未啟用sysctl_tcp_low_latency情況下,檢查tcp_low_latency,默認(rèn)其為0,表示使用prequeue隊(duì)列
if (!sysctl_tcp_low_latency && tp->ucopy.task == user_recv) {
/* Install new reader */
/*若是本次讀取的第一此檢測(cè)處理prequeue隊(duì)列,則需要設(shè)置正在讀取的進(jìn)程描述符、緩存地址信息。這樣當(dāng)
讀取進(jìn)程進(jìn)入睡眠后,ESTABLISHED狀態(tài)的接收處理就可能直接把數(shù)據(jù)復(fù)制到用戶空間*/
if (!user_recv && !(flags & (MSG_TRUNC | MSG_PEEK))) {
user_recv = current;
tp->ucopy.task = user_recv;
tp->ucopy.iov = msg->msg_iov;
}
//更新當(dāng)前可以使用的用戶緩存大小
tp->ucopy.len = len;
BUG_TRAP(tp->copied_seq == tp->rcv_nxt ||
(flags & (MSG_PEEK | MSG_TRUNC)));
//若prequeue不為空,跳轉(zhuǎn)處理prequeue隊(duì)列
if (!skb_queue_empty(&tp->ucopy.prequeue))
goto do_prequeue;
/* __ Set realtime policy in scheduler __ */
}
//若數(shù)據(jù)讀取完,調(diào)用release_sock 解鎖傳輸控制塊,主要用來(lái)處理后備隊(duì)列
if (copied >= target) {
/* Do not sleep, just process backlog. */
release_sock(sk);
//鎖定傳輸控制塊,在調(diào)用lock_sock時(shí)進(jìn)程可能會(huì)出現(xiàn)睡眠
lock_sock(sk);
} else
/*若數(shù)據(jù)未讀取,且是阻塞讀取,則進(jìn)入睡眠等待接收數(shù)據(jù)。在這種情況下,tcp_v4_do_rcv處理
tcp段時(shí)可能會(huì)把數(shù)據(jù)直接復(fù)制到用戶空間*/
sk_wait_data(sk, &timeo);
if (user_recv) {
int chunk;
/* __ Restore normal policy in scheduler __ */
//更新剩余的用戶空間長(zhǎng)度和已復(fù)制到用戶空間的數(shù)據(jù)長(zhǎng)度
if ((chunk = len - tp->ucopy.len) != 0) {
NET_ADD_STATS_USER(LINUX_MIB_TCPDIRECTCOPYFROMBACKLOG, chunk);
len -= chunk;
copied += chunk;
}
/*若接收到接收隊(duì)列中的數(shù)據(jù)已經(jīng)全部復(fù)制到用戶進(jìn)程空間,但prequeue隊(duì)列不為空,則需繼續(xù)處理prequeue隊(duì)列,
并更新剩余的用戶空間長(zhǎng)度和已復(fù)制到用戶空間的數(shù)據(jù)長(zhǎng)度*/
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隊(duì)列后,若有更新copied_seq,且只是查看數(shù)據(jù),則需要更新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;
}
//繼續(xù)獲取下一個(gè)待讀取的段作處理
continue;
found_ok_skb:
/* Ok so how much can we use? */
/*獲取該可讀取段的數(shù)據(jù)長(zhǎng)度,在前面的處理中已由tcp序號(hào)得到本次讀取數(shù)據(jù)在該段中的偏移offset*/
used = skb->len - offset;
if (len < used)
used = len;
/* Do we have urgent data here? */
//若段內(nèi)包含帶外數(shù)據(jù),則獲取帶外數(shù)據(jù)在該段中的偏移
if (tp->urg_data) {
u32 urg_offset = tp->urg_seq - *seq;
if (urg_offset < used) {
//若偏移為0,說(shuō)明目前需要的數(shù)據(jù)正是帶外數(shù)據(jù),且?guī)鈹?shù)據(jù)不允許進(jìn)入正常的數(shù)據(jù)流
if (!urg_offset) {
if (!sock_flag(sk, SOCK_URGINLINE)) {
++*seq;
offset++;
used--;
if (!used)
goto skip_copy;
}
} else
//若偏移不為0,則需要調(diào)整本次讀取的正常數(shù)據(jù)長(zhǎng)度直到讀到帶外數(shù)據(jù)為止
used = urg_offset;
}
}
//處理讀取數(shù)據(jù)的情況
if (!(flags & MSG_TRUNC)) {
{
//將數(shù)據(jù)復(fù)制到用戶空間
err = skb_copy_datagram_iovec(skb, offset,
msg->msg_iov, used);
if (err) {
/* Exception. Bailout! */
if (!copied)
copied = -EFAULT;
break;
}
}
}
*seq += used; //調(diào)整已讀取數(shù)據(jù)的序號(hào)
copied += used;//調(diào)整已讀取數(shù)據(jù)的長(zhǎng)度
len -= used;//調(diào)整剩余的可用空間緩存大小
//調(diào)整合理的tcp接收緩沖區(qū)大小
tcp_rcv_space_adjust(sk);
skip_copy:
//若對(duì)帶外數(shù)據(jù)處理完畢,則將標(biāo)志清零,
if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) {
tp->urg_data = 0;
//設(shè)置首部標(biāo)志,下一個(gè)接收段又可以通過(guò)首部預(yù)測(cè)執(zhí)行快慢速路徑
tcp_fast_path_check(sk, tp);
}
//若該段還有數(shù)據(jù)未讀取(如帶外數(shù)據(jù)),則是能繼續(xù)處理該段,而不能把該段從接收隊(duì)列中刪除
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;
}
//繼續(xù)處理后續(xù)的段
continue;
found_fin_ok:
/* Process the FIN. */
//由于fin標(biāo)志占用一個(gè)序號(hào),因此當(dāng)前讀取的序號(hào)需遞增
++*seq;
if (!(flags & MSG_PEEK)) {
sk_eat_skb(sk, skb, copied_early);
copied_early = 0;
}
//接收到fin標(biāo)志,無(wú)需繼續(xù)處理后續(xù)的段
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隊(duì)列過(guò)程中又有一部分?jǐn)?shù)據(jù)復(fù)制到用戶空間,則調(diào)整剩余的可用空間緩存大小和已讀數(shù)據(jù)的序號(hào)
if (copied > 0 && (chunk = len - tp->ucopy.len) != 0) {
NET_ADD_STATS_USER(LINUX_MIB_TCPDIRECTCOPYFROMPREQUEUE, chunk);
len -= chunk;
copied += chunk;
}
}
/*清零,表示用戶當(dāng)前沒(méi)有讀取數(shù)據(jù)。這樣當(dāng)處理prequeue隊(duì)列時(shí)不會(huì)將數(shù)據(jù)復(fù)制到用戶空間,
因?yàn)橹挥性谖磫⒂胻cp_low_latency下,用戶主動(dòng)讀取時(shí),才有機(jī)會(huì)將數(shù)據(jù)直接復(fù)制到用戶空間*/
tp->ucopy.task = NULL;
tp->ucopy.len = 0;
}
/*在完成讀取數(shù)據(jù)后,需再次檢測(cè)是否有必要立即發(fā)送ack,并根據(jù)情況確定是否發(fā)送ack段*/
tcp_cleanup_rbuf(sk, copied);
TCP_CHECK_TIMER(sk);
//返回前解鎖傳輸控制塊
release_sock(sk);
//返回已讀取的字節(jié)數(shù)
return copied;
/*若在讀取過(guò)程中發(fā)生了錯(cuò)誤,則會(huì)跳轉(zhuǎn)到此,解鎖傳輸層后返回錯(cuò)誤碼*/
out:
TCP_CHECK_TIMER(sk);
release_sock(sk);
return err;
/*若是接收帶外數(shù)據(jù),則調(diào)用tcp_recv_urg接收*/
recv_urg:
err = tcp_recv_urg(sk, timeo, msg, len, flags, addr_len);
goto out;
}
以上調(diào)使用排除帶外數(shù)據(jù)只分析正常的數(shù)據(jù)的話,處理過(guò)程如下:
1、根據(jù)尚未從內(nèi)核空間復(fù)制到用戶空間的最前面一個(gè)字節(jié)的序號(hào),找到待貝的數(shù)據(jù)塊。
2、將數(shù)據(jù)從數(shù)據(jù)塊中選擇貝到用戶空間。
3、調(diào)整合理的TCP接收綁定沖區(qū)大小
4、跳到第一步循環(huán)處理,直達(dá)到滿足用戶讀取數(shù)量的條件。
審核編輯:劉清
-
TCP
+關(guān)注
關(guān)注
8文章
1381瀏覽量
79345 -
LINUX內(nèi)核
+關(guān)注
關(guān)注
1文章
316瀏覽量
21756 -
ADD
+關(guān)注
關(guān)注
1文章
20瀏覽量
9464
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
C語(yǔ)言函數(shù)調(diào)用過(guò)程中的內(nèi)存變化解析
Linux內(nèi)核中系統(tǒng)調(diào)用詳解
![<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>中系統(tǒng)<b class='flag-5'>調(diào)用</b>詳解](https://file1.elecfans.com/web2/M00/94/68/wKgZomTlcWiAEOJgAAAQ5XaBP0g428.jpg)
Linux內(nèi)核自解壓過(guò)程分析
![<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>自解壓<b class='flag-5'>過(guò)程</b>分析](https://file1.elecfans.com/web2/M00/B3/45/wKgaomVysVSAas31AAAXBrUD8nI125.png)
Linux內(nèi)核啟動(dòng)過(guò)程和Bootloader(總述)
ARM linux系統(tǒng)調(diào)用的實(shí)現(xiàn)原理
Linux內(nèi)核系統(tǒng)調(diào)用擴(kuò)展研究
![<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>系統(tǒng)<b class='flag-5'>調(diào)用</b>擴(kuò)展研究](https://file.elecfans.com/web2/M00/49/0C/pYYBAGKhtDWAel1PAAAOzDgc90o195.jpg)
編譯Linux2.6內(nèi)核并添加一個(gè)系統(tǒng)調(diào)用
linux內(nèi)核啟動(dòng)內(nèi)核解壓過(guò)程分析
嵌入式系統(tǒng)內(nèi)核引導(dǎo)啟動(dòng)過(guò)程淺析
![嵌入式系統(tǒng)<b class='flag-5'>內(nèi)核</b>引導(dǎo)啟動(dòng)<b class='flag-5'>過(guò)程</b><b class='flag-5'>淺析</b>](https://file.elecfans.com/web2/M00/49/38/poYBAGKhwI-AGEUxAAAcp3piFBM642.png)
lattice DDR3 IP核的生成及調(diào)用過(guò)程
![lattice DDR3 IP核的生成及<b class='flag-5'>調(diào)用過(guò)程</b>](https://file.elecfans.com//web2/M00/36/39/pYYBAGIxgDuAc1OSAAPAuDo5oK4924.png)
如何區(qū)分xenomai、linux系統(tǒng)調(diào)用/服務(wù)
Linux內(nèi)核系統(tǒng)調(diào)用概述及實(shí)現(xiàn)原理
![<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>系統(tǒng)<b class='flag-5'>調(diào)用</b>概述及實(shí)現(xiàn)原理](https://file.elecfans.com//web2/M00/43/A6/poYBAGJ_SBuAMDQdAAAvuAWJ-3k473.png)
系統(tǒng)調(diào)用:用戶棧與內(nèi)核棧的切換(上)
![系統(tǒng)<b class='flag-5'>調(diào)用</b>:用戶棧與<b class='flag-5'>內(nèi)核</b>棧的切換(上)](https://file1.elecfans.com/web2/M00/8E/6D/wKgZomTHKQeARCFYAADCLKwpNvQ383.jpg)
Linux系統(tǒng)調(diào)用的具體實(shí)現(xiàn)原理
![<b class='flag-5'>Linux</b>系統(tǒng)<b class='flag-5'>調(diào)用</b>的具體實(shí)現(xiàn)原理](https://file1.elecfans.com/web2/M00/A1/B2/wKgZomT28fCAPIkHAAAd7eEyePc89.jpeg)
評(píng)論