Linux內(nèi)核源代碼:tcp/ip協(xié)議棧的調(diào)用
我們結合源碼進行仔細分析:
接收端調(diào)用的是__sys_recvfrom函數(shù):
__sys_recvfrom函數(shù)具體如下:
發(fā)現(xiàn)它調(diào)用了sock_recvmsg函數(shù):
發(fā)現(xiàn)它調(diào)用了sock_recvmsg_nosec函數(shù):
發(fā)現(xiàn)它調(diào)用了inet_recvmsg函數(shù):
最后調(diào)用的是tcp_recvmsg這個系統(tǒng)調(diào)用。至此接收端調(diào)用分析完畢。
下面用gdb打斷點進行驗證:
驗證結果剛好符合我們的調(diào)研。
4 傳輸層流程
4.1 發(fā)送端
傳輸層的最終目的是向它的用戶提供高效的、可靠的和成本有效的數(shù)據(jù)傳輸服務,主要功能包括 (1)構造 TCP segment (2)計算 checksum (3)發(fā)送回復(ACK)包 (4)滑動窗口(sliding windown)等保證可靠性的操作。TCP 協(xié)議棧的大致處理過程如下圖所示:
TCP 棧簡要過程:
tcp_sendmsg 函數(shù)會首先檢查已經(jīng)建立的 TCP connection 的狀態(tài),然后獲取該連接的 MSS,開始 segement 發(fā)送流程。
構造 TCP 段的 playload:它在內(nèi)核空間中創(chuàng)建該 packet 的 sk_buffer 數(shù)據(jù)結構的實例 skb,從 userspace buffer 中拷貝 packet 的數(shù)據(jù)到 skb 的 buffer。
構造 TCP header。
計算 TCP 校驗和(checksum)和 順序號 (sequence number)。
TCP 校驗和是一個端到端的校驗和,由發(fā)送端計算,然后由接收端驗證。其目的是為了發(fā)現(xiàn)TCP首部和數(shù)據(jù)在發(fā)送端到接收端之間發(fā)生的任何改動。如果接收方檢測到校驗和有差錯,則TCP段會被直接丟棄。TCP校驗和覆蓋 TCP 首部和 TCP 數(shù)據(jù)。
TCP的校驗和是必需的
發(fā)到 IP 層處理:調(diào)用 IP handler 句柄 ip_queue_xmit,將 skb 傳入 IP 處理流程。
UDP 棧簡要過程:
UDP 將 message 封裝成 UDP 數(shù)據(jù)報
調(diào)用 ip_append_data() 方法將 packet 送到 IP 層進行處理。
下面我們結合代碼依次分析:
根據(jù)我們對應用層的追查可以發(fā)現(xiàn),傳輸層也是先調(diào)用send()->sendto()->sys_sento->sock_sendmsg->sock_sendmsg_nosec,我們看下sock_sendmsg_nosec這個函數(shù):
在應用層調(diào)用的是inet_sendmsg函數(shù),在傳輸層根據(jù)后面的斷點可以知道,調(diào)用的是sock->ops-sendmsg這個函數(shù)。而sendmsg為一個宏,調(diào)用的是tcp_sendmsg,如下;
struct proto tcp_prot = {
.name = "TCP",
.owner = THIS_MODULE,
.close = tcp_close,
.pre_connect = tcp_v4_pre_connect,
.connect = tcp_v4_connect,
.disconnect = tcp_disconnect,
.a(chǎn)ccept = inet_csk_accept,
.ioctl = tcp_ioctl,
.init = tcp_v4_init_sock,
.destroy = tcp_v4_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.keepalive = tcp_set_keepalive,
.recvmsg = tcp_recvmsg,
.sendmsg = tcp_sendmsg,
......
而tcp_sendmsg實際上調(diào)用的是
int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
這個函數(shù)如下:
int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
{
struct tcp_sock *tp = tcp_sk(sk);進行了強制類型轉(zhuǎn)換
struct sk_buff *skb;
flags = msg->msg_flags;
......
if (copied)
tcp_push(sk, flags & ~MSG_MORE, mss_now,
TCP_NAGLE_PUSH, size_goal);
}
在tcp_sendmsg_locked中,完成的是將所有的數(shù)據(jù)組織成發(fā)送隊列,這個發(fā)送隊列是struct sock結構中的一個域sk_write_queue,這個隊列的每一個元素是一個skb,里面存放的就是待發(fā)送的數(shù)據(jù)。然后調(diào)用了tcp_push()函數(shù)。結構體struct sock如下:
struct sock{
...
struct sk_buff_head sk_write_queue;指向skb隊列的第一個元素
...
struct sk_buff *sk_send_head;指向隊列第一個還沒有發(fā)送的元素
}
在tcp協(xié)議的頭部有幾個標志字段:URG、ACK、RSH、RST、SYN、FIN,tcp_push中會判斷這個skb的元素是否需要push,如果需要就將tcp頭部字段的push置一,置一的過程如下:
static void tcp_push(struct sock *sk, int flags, int mss_now,
int nonagle, int size_goal)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb;
skb = tcp_write_queue_tail(sk);
if (!skb)
return;
if (。╢lags & MSG_MORE) || forced_push(tp))
tcp_mark_push(tp, skb);
tcp_mark_urg(tp, flags);
if (tcp_should_autocork(sk, skb, size_goal)) {
avoid atomic op if TSQ_THROTTLED bit is already set
if (!test_bit(TSQ_THROTTLED, &sk->sk_tsq_flags)) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTOCORKING);
set_bit(TSQ_THROTTLED, &sk->sk_tsq_flags);
}
It is possible TX completion already happened
* before we set TSQ_THROTTLED.
if (refcount_read(&sk->sk_wmem_alloc) > skb->truesize)
return;
}
if (flags & MSG_MORE)
nonagle = TCP_NAGLE_CORK;
__tcp_push_pending_frames(sk, mss_now, nonagle);
}
首先struct tcp_skb_cb結構體存放的就是tcp的頭部,頭部的控制位為tcp_flags,通過tcp_mark_push會將skb中的cb,也就是48個字節(jié)的數(shù)組,類型轉(zhuǎn)換為struct tcp_skb_cb,這樣位于skb的cb就成了tcp的頭部。tcp_mark_push如下:
static inline void tcp_mark_push(struct tcp_sock *tp, struct sk_buff *skb)
{
TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_PSH;
tp->pushed_seq = tp->write_seq;
}
...
#define TCP_SKB_CB(__skb) ((struct tcp_skb_cb *)&((__skb)->cb[0]))
...
struct sk_buff {
...
char cb[48] __aligned(8);
...struct tcp_skb_cb {
__u32 seq; Starting sequence number
__u32 end_seq; SEQ + FIN + SYN + datalen
__u8 tcp_flags; tcp頭部標志,位于第13個字節(jié)tcp[13])
......
};
然后,tcp_push調(diào)用了__tcp_push_pending_frames(sk, mss_now, nonagle);函數(shù)發(fā)送數(shù)據(jù):
void __tcp_push_pending_frames(struct sock *sk, unsigned int cur_mss,
int nonagle)
{
if (tcp_write_xmit(sk, cur_mss, nonagle, 0,
sk_gfp_mask(sk, GFP_ATOMIC)))
tcp_check_probe_timer(sk);
}
發(fā)現(xiàn)它調(diào)用了tcp_write_xmit函數(shù)來發(fā)送數(shù)據(jù):
static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
int push_one, gfp_t gfp)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb;
unsigned int tso_segs, sent_pkts;
int cwnd_quota;
int result;
bool is_cwnd_limited = false, is_rwnd_limited = false;
u32 max_segs;
統(tǒng)計已發(fā)送的報文總數(shù)
sent_pkts = 0;
......
若發(fā)送隊列未滿,則準備發(fā)送報文
while ((skb = tcp_send_head(sk))) {
unsigned int limit;
if (unlikely(tp->repair) && tp->repair_queue == TCP_SEND_QUEUE) {
"skb_mstamp_ns" is used as a start point for the retransmit timer
skb->skb_mstamp_ns = tp->tcp_wstamp_ns = tp->tcp_clock_cache;
list_move_tail(&skb->tcp_tsorted_anchor, &tp->tsorted_sent_queue);
tcp_init_tso_segs(skb, mss_now);
goto repair; Skip network transmission
}
if (tcp_pacing_check(sk))
break;
tso_segs = tcp_init_tso_segs(skb, mss_now);
BUG_ON(!tso_segs);
檢查發(fā)送窗口的大小
cwnd_quota = tcp_cwnd_test(tp, skb);
if (!cwnd_quota) {
if (push_one == 2)
Force out a loss probe pkt.
cwnd_quota = 1;
else
break;
}
if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now))) {
is_rwnd_limited = true;
break;
......
limit = mss_now;
if (tso_segs > 1 && !tcp_urg_mode(tp))
limit = tcp_mss_split_point(sk, skb, mss_now,
min_t(unsigned int,
cwnd_quota,
max_segs),
nonagle);
if (skb->len > limit &&
unlikely(tso_fragment(sk, TCP_FRAG_IN_WRITE_QUEUE,
skb, limit, mss_now, gfp)))
break;
if (tcp_small_queue_check(sk, skb, 0))
break;
if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))
break;
......
tcp_write_xmit位于tcpoutput.c中,它實現(xiàn)了tcp的擁塞控制,然后調(diào)用了tcp_transmit_skb(sk, skb, 1, gfp)傳輸數(shù)據(jù),實際上調(diào)用的是__tcp_transmit_skb:
static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{
skb_push(skb, tcp_header_size);
skb_reset_transport_header(skb);
......
構建TCP頭部和校驗和
th = (struct tcphdr *)skb->data;
th->source = inet->inet_sport;
th->dest = inet->inet_dport;
th->seq = htonl(tcb->seq);
th->ack_seq = htonl(rcv_nxt);
tcp_options_write((__be32 *)(th + 1), tp, &opts);
skb_shinfo(skb)->gso_type = sk->sk_gso_type;
if (likely(!(tcb->tcp_flags & TCPHDR_SYN))) {
th->window = htons(tcp_select_window(sk));
tcp_ecn_send(sk, skb, th, tcp_header_size);
} else {
RFC1323: The window in SYN & SYN/ACK segments
* is never scaled.
th->window = htons(min(tp->rcv_wnd, 65535U));
}
......
icsk->icsk_af_ops->send_check(sk, skb);
if (likely(tcb->tcp_flags & TCPHDR_ACK))
tcp_event_ack_sent(sk, tcp_skb_pcount(skb), rcv_nxt);
if (skb->len != tcp_header_size) {
tcp_event_data_sent(tp, sk);
tp->data_segs_out += tcp_skb_pcount(skb);
tp->bytes_sent += skb->len - tcp_header_size;
}
if (after(tcb->end_seq, tp->snd_nxt) || tcb->seq == tcb->end_seq)
TCP_ADD_STATS(sock_net(sk), TCP_MIB_OUTSEGS,
tcp_skb_pcount(skb));
tp->segs_out += tcp_skb_pcount(skb);
OK, its time to fill skb_shinfo(skb)->gso_{segs|size}
skb_shinfo(skb)->gso_segs = tcp_skb_pcount(skb);
skb_shinfo(skb)->gso_size = tcp_skb_mss(skb);
Leave earliest departure time in skb->tstamp (skb->skb_mstamp_ns)
Cleanup our debris for IP stacks
memset(skb->cb, 0, max(sizeof(struct inet_skb_parm),
sizeof(struct inet6_skb_parm)));
err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);
......
}
tcp_transmit_skb是tcp發(fā)送數(shù)據(jù)位于傳輸層的最后一步,這里首先對TCP數(shù)據(jù)段的頭部進行了處理,然后調(diào)用了網(wǎng)絡層提供的發(fā)送接口icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);實現(xiàn)了數(shù)據(jù)的發(fā)送,自此,數(shù)據(jù)離開了傳輸層,傳輸層的任務也就結束了。
gdb調(diào)試驗證如下:
4.2 接收端
傳輸層 TCP 處理入口在 tcp_v4_rcv 函數(shù)(位于 linux/net/ipv4/tcp ipv4.c 文件中),它會做 TCP header 檢查等處理。
調(diào)用 _tcp_v4_lookup,查找該 package 的 open socket。如果找不到,該 package 會被丟棄。接下來檢查 socket 和 connection 的狀態(tài)。
如果socket 和 connection 一切正常,調(diào)用 tcp_prequeue 使 package 從內(nèi)核進入 user space,放進 socket 的 receive queue。然后 socket 會被喚醒,調(diào)用 system call,并最終調(diào)用 tcp_recvmsg 函數(shù)去從 socket recieve queue 中獲取 segment。
對于傳輸層的代碼階段,我們需要分析recv函數(shù),這個與send類似,調(diào)用的是__sys_recvfrom,整個函數(shù)的調(diào)用路徑與send非常類似:
int __sys_recvfrom(int fd, void __user *ubuf, size_t size, unsigned int flags,
struct sockaddr __user *addr, int __user *addr_len)
{
......
err = import_single_range(READ, ubuf, size, &iov, &msg.msg_iter);
if (unlikely(err))
return err;
sock = sockfd_lookup_light(fd, &err, &fput_needed);
.....
msg.msg_control = NULL;
msg.msg_controllen = 0;
Save some cycles and don't copy the address if not needed
msg.msg_name = addr ? (struct sockaddr *)&address : NULL;
We assume all kernel code knows the size of sockaddr_storage
msg.msg_namelen = 0;
msg.msg_iocb = NULL;
msg.msg_flags = 0;
if (sock->file->f_flags & O_NONBLOCK)
flags |= MSG_DONTWAIT;
err = sock_recvmsg(sock, &msg, flags);
if (err >= 0 && addr 。 NULL) {
err2 = move_addr_to_user(&address,
msg.msg_namelen, addr, addr_len);
.....
}
__sys_recvfrom調(diào)用了sock_recvmsg來接收數(shù)據(jù),整個函數(shù)實際調(diào)用的是sock->ops->recvmsg(sock, msg, msg_data_left(msg), flags);,同樣,根據(jù)tcp_prot結構的初始化,調(diào)用的其實是tcp_rcvmsg
接受函數(shù)比發(fā)送函數(shù)要復雜得多,因為數(shù)據(jù)接收不僅僅只是接收,tcp的三次握手也是在接收函數(shù)實現(xiàn)的,所以收到數(shù)據(jù)后要判斷當前的狀態(tài),是否正在建立連接等,根據(jù)發(fā)來的信息考慮狀態(tài)是否要改變,在這里,我們僅僅考慮在連接建立后數(shù)據(jù)的接收。
tcp_rcvmsg函數(shù)如下:
int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock,
int flags, int *addr_len)
{
......
if (sk_can_busy_loop(sk) && skb_queue_empty(&sk->sk_receive_queue) &&
(sk->sk_state == TCP_ESTABLISHED))
sk_busy_loop(sk, nonblock);
lock_sock(sk);
.....
if (unlikely(tp->repair)) {
err = -EPERM;
if (。╢lags & MSG_PEEK))
goto out;
if (tp->repair_queue == TCP_SEND_QUEUE)
goto recv_sndq;
err = -EINVAL;
if (tp->repair_queue == TCP_NO_QUEUE)
goto out;
......
last = skb_peek_tail(&sk->sk_receive_queue);
skb_queue_walk(&sk->sk_receive_queue, skb) {
last = skb;
......
if (!(flags & MSG_TRUNC)) {
err = skb_copy_datagram_msg(skb, offset, msg, used);
if (err) {
Exception. Bailout!
if (!copied)
copied = -EFAULT;
break;
}
}
*seq += used;
copied += used;
len -= used;
tcp_rcv_space_adjust(sk);
請輸入評論內(nèi)容...
請輸入評論/評論長度6~500個字
最新活動更多
-
10月31日立即下載>> 【限時免費下載】TE暖通空調(diào)系統(tǒng)高效可靠的組件解決方案
-
即日-11.13立即報名>>> 【在線會議】多物理場仿真助跑新能源汽車
-
11月28日立即報名>>> 2024工程師系列—工業(yè)電子技術在線會議
-
12月19日立即報名>> 【線下會議】OFweek 2024(第九屆)物聯(lián)網(wǎng)產(chǎn)業(yè)大會
-
即日-12.26火熱報名中>> OFweek2024中國智造CIO在線峰會
-
即日-2025.8.1立即下載>> 《2024智能制造產(chǎn)業(yè)高端化、智能化、綠色化發(fā)展藍皮書》
推薦專題
- 高級軟件工程師 廣東省/深圳市
- 自動化高級工程師 廣東省/深圳市
- 光器件研發(fā)工程師 福建省/福州市
- 銷售總監(jiān)(光器件) 北京市/海淀區(qū)
- 激光器高級銷售經(jīng)理 上海市/虹口區(qū)
- 光器件物理工程師 北京市/海淀區(qū)
- 激光研發(fā)工程師 北京市/昌平區(qū)
- 技術專家 廣東省/江門市
- 封裝工程師 北京市/海淀區(qū)
- 結構工程師 廣東省/深圳市