應(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ù)采集到用戶(hù)文件夾中。

tcp_recvmsg
//把數(shù)據(jù)從接收隊(duì)列中復(fù)制到用戶(hù)空間中
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ù)到用戶(hù)空間,會(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)度為用戶(hù)調(diào)用時(shí)的輸入?yún)?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;
//若用戶(hù)進(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ù)制到用戶(hù)空間*/
if (!user_recv && !(flags & (MSG_TRUNC | MSG_PEEK))) {
user_recv = current;
tp->ucopy.task = user_recv;
tp->ucopy.iov = msg->msg_iov;
}
//更新當(dāng)前可以使用的用戶(hù)緩存大小
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ù)制到用戶(hù)空間*/
sk_wait_data(sk, &timeo);
if (user_recv) {
int chunk;
/* __ Restore normal policy in scheduler __ */
//更新剩余的用戶(hù)空間長(zhǎng)度和已復(fù)制到用戶(hù)空間的數(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ù)制到用戶(hù)進(jìn)程空間,但prequeue隊(duì)列不為空,則需繼續(xù)處理prequeue隊(duì)列,
并更新剩余的用戶(hù)空間長(zhǎng)度和已復(fù)制到用戶(hù)空間的數(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ù)制到用戶(hù)空間
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ù)制到用戶(hù)空間,則調(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;
}
}
/*清零,表示用戶(hù)當(dāng)前沒(méi)有讀取數(shù)據(jù)。這樣當(dāng)處理prequeue隊(duì)列時(shí)不會(huì)將數(shù)據(jù)復(fù)制到用戶(hù)空間,
因?yàn)橹挥性谖磫⒂胻cp_low_latency下,用戶(hù)主動(dòng)讀取時(shí),才有機(jī)會(huì)將數(shù)據(jù)直接復(fù)制到用戶(hù)空間*/
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ù)制到用戶(hù)空間的最前面一個(gè)字節(jié)的序號(hào),找到待貝的數(shù)據(jù)塊。
2、將數(shù)據(jù)從數(shù)據(jù)塊中選擇貝到用戶(hù)空間。
3、調(diào)整合理的TCP接收綁定沖區(qū)大小
4、跳到第一步循環(huán)處理,直達(dá)到滿足用戶(hù)讀取數(shù)量的條件。
審核編輯:劉清
聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。
舉報(bào)投訴
-
TCP
+關(guān)注
關(guān)注
8文章
1424瀏覽量
83500 -
LINUX內(nèi)核
+關(guān)注
關(guān)注
1文章
321瀏覽量
23202 -
ADD
+關(guān)注
關(guān)注
1文章
20瀏覽量
9737
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
熱點(diǎn)推薦
C語(yǔ)言函數(shù)調(diào)用過(guò)程中的內(nèi)存變化解析
相信很多編程新手村的同學(xué)們都會(huì)有一個(gè)疑問(wèn):C 語(yǔ)言如何調(diào)用函數(shù)的呢?局部變量的作用域?yàn)槭裁磧H限于函數(shù)內(nèi)?這個(gè)調(diào)用不是指C 語(yǔ)言上的函數(shù)調(diào)用的語(yǔ)法,而是在內(nèi)存的視角下,函數(shù)的調(diào)用過(guò)程。本
Linux內(nèi)核中系統(tǒng)調(diào)用詳解
Linux內(nèi)核中設(shè)置了一組用于實(shí)現(xiàn)各種系統(tǒng)功能的子程序,稱(chēng)為系統(tǒng)調(diào)用。用戶(hù)可以通過(guò)系統(tǒng)調(diào)用命令在自己的應(yīng)用程序中調(diào)用它們。從某種角度來(lái)看,系
發(fā)表于 08-23 10:37
?1283次閱讀
Linux內(nèi)核自解壓過(guò)程分析
uboot完成系統(tǒng)引導(dǎo)以后,執(zhí)行環(huán)境變量bootm中的命令;即,將Linux內(nèi)核調(diào)入內(nèi)存中并調(diào)用do_bootm函數(shù)啟動(dòng)內(nèi)核,跳轉(zhuǎn)至kernel的起始位置。
Linux內(nèi)核啟動(dòng)過(guò)程和Bootloader(總述)
,所以一般的 Bootloader 都會(huì)在執(zhí)行過(guò)程中初始化一個(gè)串口做為調(diào)試端口(3)檢測(cè)處理器類(lèi)型 Bootloader在調(diào)用 Linux內(nèi)核前必須檢測(cè)系統(tǒng)的處理器類(lèi)型,并將其保存到某
發(fā)表于 08-18 17:35
ARM linux系統(tǒng)調(diào)用的實(shí)現(xiàn)原理
大家都知道linux的應(yīng)用程序要想訪問(wèn)內(nèi)核必須使用系統(tǒng)調(diào)用從而實(shí)現(xiàn)從usr模式轉(zhuǎn)到svc模式。下面咱們看看它的實(shí)現(xiàn)過(guò)程。
發(fā)表于 05-30 11:24
?2423次閱讀
Linux內(nèi)核系統(tǒng)調(diào)用擴(kuò)展研究
系統(tǒng)凋用是操作系統(tǒng)內(nèi)核提供給用戶(hù)使用內(nèi)核服務(wù)的接口。LinuX操作系統(tǒng)由于其自由開(kāi)放性,用戶(hù)可在原有基礎(chǔ)上,添加新的系統(tǒng)調(diào)用,以便提供更多的服務(wù)?;贚inttx2.4
發(fā)表于 07-25 16:09
?40次下載
編譯Linux2.6內(nèi)核并添加一個(gè)系統(tǒng)調(diào)用
本文以實(shí)例來(lái)詳細(xì)描述了從準(zhǔn)備一直到使用新內(nèi)核的Linux2.6 內(nèi)核編譯過(guò)程,然后介紹了添加系統(tǒng)調(diào)用的實(shí)現(xiàn)步驟,最后給實(shí)驗(yàn)結(jié)果。
發(fā)表于 12-01 15:54
?46次下載
linux內(nèi)核啟動(dòng)內(nèi)核解壓過(guò)程分析
linux啟動(dòng)時(shí)內(nèi)核解壓過(guò)程分析,一份不錯(cuò)的文檔,深入了解內(nèi)核必備
發(fā)表于 03-09 13:39
?1次下載
嵌入式系統(tǒng)內(nèi)核引導(dǎo)啟動(dòng)過(guò)程淺析
嵌入式系統(tǒng)內(nèi)核引導(dǎo)啟動(dòng)過(guò)程淺析
發(fā)表于 10-30 10:26
?6次下載
lattice DDR3 IP核的生成及調(diào)用過(guò)程
本文以一個(gè)案例的形式來(lái)介紹lattice DDR3 IP核的生成及調(diào)用過(guò)程,同時(shí)介紹各個(gè)接口信號(hào)的功能作用
發(fā)表于 03-16 14:14
?2767次閱讀
如何區(qū)分xenomai、linux系統(tǒng)調(diào)用/服務(wù)
對(duì)于同一個(gè)POSIX接口應(yīng)用程序,可能既需要xenomai內(nèi)核提供服務(wù)(xenomai 系統(tǒng)調(diào)用),又需要調(diào)用linux內(nèi)核提供服務(wù)(
Linux內(nèi)核系統(tǒng)調(diào)用概述及實(shí)現(xiàn)原理
本文介紹了系統(tǒng)調(diào)用的一些實(shí)現(xiàn)細(xì)節(jié)。首先分析了系統(tǒng)調(diào)用的意義,它們與庫(kù)函數(shù)和應(yīng)用程序接口(API)有怎樣的關(guān)系。然后,我們考察了Linux內(nèi)核如何實(shí)現(xiàn)系統(tǒng)
系統(tǒng)調(diào)用:用戶(hù)棧與內(nèi)核棧的切換(上)
sysenter / sysexit 再到 syscall / sysret 實(shí)現(xiàn)方式的轉(zhuǎn)變,關(guān)于具體的演化和區(qū)別、系統(tǒng)調(diào)用的其他細(xì)節(jié)等將在以后的系統(tǒng)調(diào)用專(zhuān)欄里分析。本文從系統(tǒng)調(diào)用最原始的int 0x80開(kāi)始分析用戶(hù)棧與
Linux系統(tǒng)調(diào)用的具體實(shí)現(xiàn)原理
文我將基于 ARM 體系結(jié)構(gòu)角度,從 Linux 應(yīng)用層例子到內(nèi)核系統(tǒng)調(diào)用函數(shù)的整個(gè)過(guò)程來(lái)梳理一遍,講清楚linux系統(tǒng)
淺析Linux內(nèi)核數(shù)調(diào)用過(guò)程
評(píng)論