1. 程式人生 > >TCP協議傳送函式tcp_sendmsg

TCP協議傳送函式tcp_sendmsg

TCP協議傳送過程的資料包來源有兩種,第一是應用層產生的資料包需要複製到核心接受緩衝區由tcp_sendmsg函式完成,第二是TCP連線管理TCP協議層自己闡述的資料、資料包重傳資料包,由函式tcp_transmit_skb完成。首先介紹tcp_sendmsg函式,tcp_sendmsg函式主要做了三件事情:

(1)、將資料包複製到Socket Buffer中。

(2)、把Socket BUffer鍵入到傳送佇列。

(3)、設定TCP控制塊結構,用於構造TCP協議頭資訊。

1、判斷套接字狀態

傳送資料包時首先要檢查套接字狀態,如果不是TCP_ESTABLISHED、或者TCP_CLOSE_WAIT就直接返回,表名連線還沒有建立不能傳送資料包,接著初始化一些區域性變數,iov應用層資料庫起始地址、iovlen資料庫個數、mss_now當前開啟的套接字最大長度、timeo傳送超時時間

int tcp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
		size_t size)
{
	struct sock *sk = sock->sk;
	struct iovec *iov;
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *skb;
	int iovlen, flags;
	int mss_now, size_goal;
	int sg, err, copied;
	long timeo;

	//鎖定套接字
	lock_sock(sk);
	TCP_CHECK_TIMER(sk);

	flags = msg->msg_flags;
	//設定超時時間
	timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);

	/* Wait for a connection to finish. */
	//不是狀態不是ESTABLISHED和CLOSE_WAIT表示還沒有建立連線
	//直接返回
	if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
		if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)
			goto out_err;

	/* This should be in poll */
	clear_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);

	//可傳送TCP最大資料包長度,根據mtu決定
	mss_now = tcp_send_mss(sk, &size_goal, flags);

	/* Ok commence sending. */
	//儲存資料在使用者空間地址
	iovlen = msg->msg_iovlen;
	//msg_iov資料格式的數量
	iov = msg->msg_iov;
	copied = 0;

	err = -EPIPE;
	if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
		goto out_err;

	sg = sk->sk_route_caps & NETIF_F_SG;

...

2、初始化Socket Buffer

接下來進入主迴圈,複製資料到核心緩衝區直到seglen等於0,首先將指標偏移到最後一個Socket Buffer處,呼叫sk_stream_memory_free判斷髮送緩衝區是否有剩餘空間,如果沒有就等待資料傳送,如果還有剩餘空間就呼叫sk_stream_alloc_skb分配一個Socket Buffer,然後把這個skb加入到佇列中

...
	
	while (seglen > 0) {
			int copy = 0;
			int max = size_goal;

			//指向最後一個skb
			skb = tcp_write_queue_tail(sk);
			if (tcp_send_head(sk)) {
				//佇列中沒有資料包
				if (skb->ip_summed == CHECKSUM_NONE)
					max = mss_now;
				//剩餘空間大小
				copy = max - skb->len;
			}

			if (copy <= 0) {
//分配新的記憶體存放資料
new_segment:
				/* Allocate new segment. If the interface is SG,
				 * allocate skb fitting to single page.
				 */
				 //沒有記憶體可以分配,就等待記憶體是否
				if (!sk_stream_memory_free(sk))
					goto wait_for_sndbuf;

				//分配socket buffer
				skb = sk_stream_alloc_skb(sk,
							  select_size(sk, sg),
							  sk->sk_allocation);
				if (!skb)
					goto wait_for_memory;

				/*
				 * Check whether we can use HW checksum.
				 */
				if (sk->sk_route_caps & NETIF_F_ALL_CSUM)
					skb->ip_summed = CHECKSUM_PARTIAL;
				//將新分配的skb加入到佇列中
				skb_entail(sk, skb);
				copy = size_goal;
				max = size_goal;
			}
...

3、複製資料到核心緩衝區

這裡我們要知道資料包存放的位置有兩個地方,第一個是Socket Buffer裡面緩衝區也就是線性儲存,第二是struct skb_shared_info中的frags頁面陣列稱為非線性儲存。資料包先儲存在Socket Buffer線性儲存中,如果線性儲存沒有空間了就儲存在struct skb_shared_info的frags陣列中。所以先呼叫skb_tailroom檢視skb緩衝區是否有空間,如果有就呼叫skb_add_data將資料包複製到Socket Buffer緩衝區空間,如果Socket Buffer沒有空間了就檢視struct skb_shared_infof的資料結構frags頁面陣列的最後一個頁面是否還有空間,呼叫skb_can_coalesce計算頁面是否還有剩餘空間,如果有就返回第i個頁面,最後要更新第i個頁面中儲存資料長度。如果frags最後一個頁面沒有剩餘空間就呼叫sk_stream_alloc_page分配一個新的頁。

...

/* Where to copy to? */
			//判斷socket buffer的緩衝區空間是否還有剩餘空間
			if (skb_tailroom(skb) > 0) {
				/* We have some space in skb head. Superb! */
				if (copy > skb_tailroom(skb))
					//返回剩餘空間大小
					copy = skb_tailroom(skb);
				//最終呼叫copy_from_user將資料包複製
				//到剩餘空間
				if ((err = skb_add_data(skb, from, copy)) != 0)
					goto do_fault;
			} else {
				int merge = 0;
				
				int i = skb_shinfo(skb)->nr_frags;
				struct page *page = TCP_PAGE(sk);
				int off = TCP_OFF(sk);
				//判斷frags頁面陣列中是否還有空間
				if (skb_can_coalesce(skb, i, page, off) &&
				    off != PAGE_SIZE) {
					/* We can extend the last page
					 * fragment. */
					merge = 1;
				} else if (i == MAX_SKB_FRAGS || !sg) {
					/* Need to add new fragment and cannot
					 * do this because interface is non-SG,
					 * or because all the page slots are
					 * busy. */
					 //頁面數量已經達到最大,這隻PSH標誌
					 //表示資料段可以傳送了
					tcp_mark_push(tp, skb);
					goto new_segment;
				} else if (page) {
					if (off == PAGE_SIZE) {
						put_page(page);
						TCP_PAGE(sk) = page = NULL;
						off = 0;
					}
				} else
					off = 0;

				if (copy > PAGE_SIZE - off)
					copy = PAGE_SIZE - off;

				if (!sk_wmem_schedule(sk, copy))
					goto wait_for_memory;

				if (!page) {
					/* Allocate new cache page. */
					//頁面陣列中最後一個頁面也滿了就分配一個新頁
					if (!(page = sk_stream_alloc_page(sk)))
						goto wait_for_memory;
				}

				/* Time to copy data. We are close to
				 * the end! */
				 //將資料複製到頁緩衝區
				err = skb_copy_to_page(sk, from, skb, page,
						       off, copy);
				if (err) {
					/* If this page was new, give it to the
					 * socket so it does not get leaked.
					 */
					if (!TCP_PAGE(sk)) {
						TCP_PAGE(sk) = page;
						TCP_OFF(sk) = 0;
					}
					goto do_error;
				}

				/* Update the skb. */
				if (merge) {
					//更新第i個頁面儲存的資料大小
					skb_shinfo(skb)->frags[i - 1].size +=
									copy;
				} else {
					//新增頁面則要填充頁面描述資訊
					skb_fill_page_desc(skb, i, page, off, copy);
					if (TCP_PAGE(sk)) {
						get_page(page);
					} else if (off + copy < PAGE_SIZE) {
						get_page(page);
						TCP_PAGE(sk) = page;
					}
				}

				TCP_OFF(sk) = off + copy;
			}

			//TCP協議頭資訊設定在socket buffer 控制緩衝區
			//當資料段傳送時才穿件TCP協議頭
			if (!copied)
				TCP_SKB_CB(skb)->flags &= ~TCPCB_FLAG_PSH;

			//寫更新序列號
			tp->write_seq += copy;
			//更新緩衝區中的序列號
			TCP_SKB_CB(skb)->end_seq += copy;
			skb_shinfo(skb)->gso_segs = 0;

			from += copy;
			copied += copy;
			if ((seglen -= copy) == 0 && iovlen == 0)
				goto out;

			if (skb->len < max || (flags & MSG_OOB))
				continue;

...

4、傳送資料

TCP協議頭資訊不設定帶資料包中,而是儲存在Socket Bufer控制緩衝區,當有資料段從佇列取出傳送時才建立TCP協議頭,force_push檢視是否立即傳送資料包,如果是就呼叫tcp_mack_push設定TCP協議頭PSH標誌,然後立即傳送資料,如果佇列中還沒有足夠的緩衝區或緩衝頁面就等待資料包到達一定資料再發送,最終呼叫的傳送函式是tcp_transmit_skb。

...
	
		//檢視是否立即傳送資料包
			if (forced_push(tp)) {
				//設定立即傳送資料包標誌PSH
				tcp_mark_push(tp, skb);
				//傳送資料包,實際呼叫的是ip_queue_xmit
				__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);
			} else if (skb == tcp_send_head(sk))
				tcp_push_one(sk, mss_now);
			continue;

wait_for_sndbuf:
			//緩衝資料段,等到一定數量再發送
			set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
wait_for_memory:
			if (copied)
				tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);

			if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)
				goto do_error;

			mss_now = tcp_send_mss(sk, &size_goal, flags);
		}
	}
...

tcp_sendmsg完整程式碼:

int tcp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
		size_t size)
{
	struct sock *sk = sock->sk;
	struct iovec *iov;
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *skb;
	int iovlen, flags;
	int mss_now, size_goal;
	int sg, err, copied;
	long timeo;

	//鎖定套接字
	lock_sock(sk);
	TCP_CHECK_TIMER(sk);

	flags = msg->msg_flags;
	//設定超時時間
	timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);

	/* Wait for a connection to finish. */
	//不是狀態不是ESTABLISHED和CLOSE_WAIT表示還沒有建立連線
	//直接返回
	if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
		if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)
			goto out_err;

	/* This should be in poll */
	clear_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);

	//可傳送TCP最大資料包長度,根據mtu決定
	mss_now = tcp_send_mss(sk, &size_goal, flags);

	/* Ok commence sending. */
	//儲存資料在使用者空間地址
	iovlen = msg->msg_iovlen;
	//msg_iov資料格式的數量
	iov = msg->msg_iov;
	copied = 0;

	err = -EPIPE;
	if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
		goto out_err;

	sg = sk->sk_route_caps & NETIF_F_SG;

	while (--iovlen >= 0) {
		//i/o陣列中元素的個數
		int seglen = iov->iov_len;
		//i/o陣列基地址
		unsigned char __user *from = iov->iov_base;

		iov++;

		while (seglen > 0) {
			int copy = 0;
			int max = size_goal;

			//指向最後一個skb
			skb = tcp_write_queue_tail(sk);
			if (tcp_send_head(sk)) {
				//佇列中沒有資料包
				if (skb->ip_summed == CHECKSUM_NONE)
					max = mss_now;
				//剩餘空間大小
				copy = max - skb->len;
			}

			if (copy <= 0) {
//分配新的記憶體存放資料
new_segment:
				/* Allocate new segment. If the interface is SG,
				 * allocate skb fitting to single page.
				 */
				 //判斷髮送緩衝區是否還有空閒記憶體
				 //如果沒有就等待資料傳送
				if (!sk_stream_memory_free(sk))
					goto wait_for_sndbuf;

				//分配socket buffer
				skb = sk_stream_alloc_skb(sk,
							  select_size(sk, sg),
							  sk->sk_allocation);
				if (!skb)
					goto wait_for_memory;

				/*
				 * Check whether we can use HW checksum.
				 */
				if (sk->sk_route_caps & NETIF_F_ALL_CSUM)
					skb->ip_summed = CHECKSUM_PARTIAL;
				//將新分配的skb加入到佇列中
				skb_entail(sk, skb);
				copy = size_goal;
				max = size_goal;
			}

			/* Try to append data to the end of skb. */
			if (copy > seglen)
				copy = seglen;

			/* Where to copy to? */
			//判斷socket buffer的緩衝區空間是否還有剩餘空間
			if (skb_tailroom(skb) > 0) {
				/* We have some space in skb head. Superb! */
				if (copy > skb_tailroom(skb))
					//返回剩餘空間大小
					copy = skb_tailroom(skb);
				//最終呼叫copy_from_user將資料包複製
				//到剩餘空間
				if ((err = skb_add_data(skb, from, copy)) != 0)
					goto do_fault;
			} else {
				int merge = 0;
				
				int i = skb_shinfo(skb)->nr_frags;
				struct page *page = TCP_PAGE(sk);
				int off = TCP_OFF(sk);
				//判斷frags頁面陣列中是否還有空間
				//並返回第i個頁面
				if (skb_can_coalesce(skb, i, page, off) &&
				    off != PAGE_SIZE) {
					/* We can extend the last page
					 * fragment. */
					merge = 1;
				} else if (i == MAX_SKB_FRAGS || !sg) {
					/* Need to add new fragment and cannot
					 * do this because interface is non-SG,
					 * or because all the page slots are
					 * busy. */
					 //頁面數量已經達到最大,這隻PSH標誌
					 //表示資料段可以傳送了
					tcp_mark_push(tp, skb);
					goto new_segment;
				} else if (page) {
					if (off == PAGE_SIZE) {
						put_page(page);
						TCP_PAGE(sk) = page = NULL;
						off = 0;
					}
				} else
					off = 0;

				if (copy > PAGE_SIZE - off)
					copy = PAGE_SIZE - off;

				if (!sk_wmem_schedule(sk, copy))
					goto wait_for_memory;

				if (!page) {
					/* Allocate new cache page. */
					//頁面陣列中最後一個頁面也滿了就分配一個新頁
					if (!(page = sk_stream_alloc_page(sk)))
						goto wait_for_memory;
				}

				/* Time to copy data. We are close to
				 * the end! */
				 //將資料複製到頁緩衝區
				err = skb_copy_to_page(sk, from, skb, page,
						       off, copy);
				if (err) {
					/* If this page was new, give it to the
					 * socket so it does not get leaked.
					 */
					if (!TCP_PAGE(sk)) {
						TCP_PAGE(sk) = page;
						TCP_OFF(sk) = 0;
					}
					goto do_error;
				}

				/* Update the skb. */
				if (merge) {
					//更新第i個頁面儲存的資料大小
					skb_shinfo(skb)->frags[i - 1].size +=
									copy;
				} else {
					//新增頁面則要填充頁面描述資訊
					skb_fill_page_desc(skb, i, page, off, copy);
					if (TCP_PAGE(sk)) {
						get_page(page);
					} else if (off + copy < PAGE_SIZE) {
						get_page(page);
						TCP_PAGE(sk) = page;
					}
				}

				TCP_OFF(sk) = off + copy;
			}

			//TCP協議頭資訊設定在socket buffer 控制緩衝區
			//當資料段傳送時才穿件TCP協議頭
			if (!copied)
				TCP_SKB_CB(skb)->flags &= ~TCPCB_FLAG_PSH;

			//寫更新序列號
			tp->write_seq += copy;
			//更新緩衝區中的序列號
			TCP_SKB_CB(skb)->end_seq += copy;
			skb_shinfo(skb)->gso_segs = 0;

			from += copy;
			copied += copy;
			if ((seglen -= copy) == 0 && iovlen == 0)
				goto out;

			if (skb->len < max || (flags & MSG_OOB))
				continue;

			//檢視是否立即傳送資料包
			if (forced_push(tp)) {
				//設定立即傳送資料包標誌PSH
				tcp_mark_push(tp, skb);
				//傳送資料包,實際呼叫的是ip_queue_xmit
				__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);
			} else if (skb == tcp_send_head(sk))
				tcp_push_one(sk, mss_now);
			continue;

wait_for_sndbuf:
			//緩衝資料段,等到一定數量再發送
			set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
wait_for_memory:
			if (copied)
				tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);

			if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)
				goto do_error;

			mss_now = tcp_send_mss(sk, &size_goal, flags);
		}
	}

out:
	if (copied)
		tcp_push(sk, flags, mss_now, tp->nonagle);
	TCP_CHECK_TIMER(sk);
	release_sock(sk);

	if (copied > 0)
		uid_stat_tcp_snd(current_uid(), copied);
	return copied;

do_fault:
	if (!skb->len) {
		tcp_unlink_write_queue(skb, sk);
		/* It is the one place in all of TCP, except connection
		 * reset, where we can be unlinking the send_head.
		 */
		tcp_check_send_head(sk, skb);
		sk_wmem_free_skb(sk, skb);
	}

do_error:
	if (copied)
		goto out;
out_err:
	err = sk_stream_error(sk, flags, err);
	TCP_CHECK_TIMER(sk);
	release_sock(sk);
	return err;
}