1. 程式人生 > >linux TCP傳送原始碼學習(1)--tcp_sendmsg

linux TCP傳送原始碼學習(1)--tcp_sendmsg

一、tcp_sendmsg()函式分析:


  1. int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
  2.         size_t size)
  3. {
  4.     struct iovec *iov;
  5.     /*從通用的struct sock *sk得到struct tcp_sock *tp,其實只是一個強制型別轉換,因為strcut sock是所有其它socket型別的第一個成員,所有可以直接對指標進行強制型別轉換*/
  6.     struct tcp_sock *tp =
     tcp_sk(sk);
  7.     struct sk_buff *skb;
  8.     int iovlen, flags;
  9.     int mss_now, size_goal;
  10.     int sg, err, copied;
  11.     long timeo;
  12.     lock_sock(sk);
  13.     TCP_CHECK_TIMER(sk);
  14.     flags = msg->msg_flags;
  15.   /*設定傳送等待時間,如果設定了DONTWAIT,則timeo為0. 如果沒有該標誌,則timeo就為sock->sk_sndtimeo。
  16. 傳送超時時間儲存在sock結構的sk_sndtimeo成員中。*/
  17.     timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
  18.     /* Wait for a connection to finish. */
  19.   /*TCP只在ESTABLISHED和CLOSE_WAIT這兩種狀態下,接收視窗是開啟的,才能接收資料。
  20. 因此如果不處於這兩種狀態,則呼叫sk_stream_wait_connect()等待建立起連線,一旦超時則跳轉到out_err處做出錯處理*/
  21.     if ((<< sk->sk_state)
     & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
  22.         if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)
  23.             goto out_err;
  24.     /* This should be in poll */
  25.   /*清除套介面傳送緩衝佇列已滿的標誌。
  26.     struct socket ->flags一組標誌位,如下:
  27.     SOCK_ASYNC_NOSPACE:標識該套介面的傳送佇列是否已滿。
  28.     SOCK_ASYNC_WAITDATA:標識應用程式通過recv呼叫時,是否在等待資料的接收。
  29.     SOCK_NOSPACE:標識非非同步的情況下該套介面的傳送佇列是否已滿。
  30.     SOCK_PASSCRED:用於標識是否設定了SO_PASSCRE套介面選項。
  31.     SOCK_PASSSEC:用於標識是否設定了SO_PASSSEC選項。*/
  32. clear_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
  33.   /*呼叫tcp_send_mss獲取當前有效mss即mss_now和資料段的最大長度即size_goal。
  34.     在此傳入是否標識MSG_OOB位,這是因為MSG_OOB是判斷是否支援GSO的條件之一,而緊急資料不支援GSO。
  35.     mss_now:當前的最大報文分段長度(Maxitum Segment Size)。
  36.     size_goal:傳送資料報到達網路裝置時資料段的最大長度,該長度用來分割資料。TCP傳送報文時,每個SKB的大小不能超過該值。
  37.     在不支援GSO的情況下,size_goal就等於mss_now,而如果支援GSO,則size_goal會是MSS的整數倍。資料報傳送到網路裝置後再由網路裝置根據MSS進行分割。*/
  38.     mss_now = tcp_send_mss(sk, &size_goal, flags);
  39.     /* Ok commence sending. */
  40.     /*獲取待發送資料塊塊數和資料指標,同時清零copied,copied是已經從使用者資料塊複製到SKB的位元組數。*/
  41.     iovlen = msg->msg_iovlen;
  42.     iov = msg->msg_iov;
  43.     copied = 0;
  44. /*在開始分段前,初始化錯誤碼為EPIPE,然後判斷此時套介面是否存在錯誤,以及該套介面是否允許傳送資料,如果有錯誤或不允許傳送資料,則跳轉到do_err處做出錯處理。*/
  45.     err = -EPIPE;
  46.     if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
  47.         goto out_err;
  48. /*獲取裝置是否支援離散聚合屬性。*/
  49.     sg = sk->sk_route_caps & NETIF_F_SG;
  50.   /*分段過程是由兩個迴圈來控制的,外層迴圈控制是否所有使用者資料塊都已複製完成。
  51. 首先獲取每個資料塊的長度及指標,同時將資料塊指標指向下一個資料塊,為複製下一個資料塊做準備。*/
  52.     while (--iovlen >= 0) {
  53.         size_t seglen = iov->iov_len;
  54.         unsigned char __user *from = iov->iov_base;
  55.         iov++;
  56. /*分段過程的內層迴圈控制每個資料塊是否複製完成。*/
  57.         while (seglen > 0) {
  58.             int copy = 0;
  59.             int max = size_goal;
  60.             /*max=size_goal=tp->xmit_size_goal,表示傳送資料報到達網路裝置時資料段的最大長度,該長度用來分割資料,TCP傳送報文時,每個SKB的大小不能超過該值。
  61.             在不支援GSO情況下,xmit_size_goal就等於MSS;而如果支援GSO,則xmit_size_goal會是MSS的整數倍。資料報傳送到網路裝置後再由網路裝置根據MSS進行分割。*/
  62. /*獲取傳輸控制塊傳送佇列的尾部的那個SKB,因為只有隊尾的那個SKB才有可能存在剩餘空間的。*/
  63.             skb = tcp_write_queue_tail(sk);
  64.             if (tcp_send_head(sk)) {
  65.                 if (skb->ip_summed == CHECKSUM_NONE)//校驗和的相關知識詳見段三。
  66.                     max = mss_now;
  67.     /*本次迴圈copy的資料長度。max是當前SKB的最大資料長度,skb->len是當前skb的資料長度,相減得到當前skb的剩餘資料空間。*/
  68.                 copy = max - skb->len;
  69.             }
  70. /*copy小於等於0,說明當前SKB已使用空間大於等於size_goal,則需要分配新的SKB*/
  71.             if (copy <= 0) {
  72. new_segment:
  73.                 /* Allocate new segment. If the interface is SG,
  74.                  * allocate skb fitting to single page.
  75.                  */
  76.               /*判斷髮送佇列中報文總長度是否已達到傳送緩衝區的上限,如果超過,則只能跳到wait_for_sndbuf處理。
  77.                 sk_stream_memory_free中兩個引數的說明:
  78.                 sk->sk_wmem_queued:表示傳送緩衝佇列中已分配的位元組數,一般來說,分配一個struct sk_buff是用於存放一個tcp資料報,其分配位元組數應該是MSS+協議首部長度。
  79.                     在我的實驗環境中,MSS值是1448,協議首部取最大長度 MAX_TCP_HEADER,在我的實驗環境中為224。經資料對齊處理後,最後struct sk_buff的truesize為1956。
  80.                     也就是佇列中每分配一個struct sk_buff,sk->sk_wmem_queued的值就增加1956。
  81.                 sk->sk_rcvbuf、sk->sk_sndbuf,這兩個值分別代表每個sock的接收發送佇列的最大限制。*/
  82.                 if (!sk_stream_memory_free(sk))
  83.                     goto wait_for_sndbuf;
  84. /*開始alloc一個新的skb,alloc的大小一般都等於mss的大小,這裡是通過select_size得到的。*/
  85.                 skb = sk_stream_alloc_skb(sk,
  86.                              select_size(sk, sg),
  87.                              sk->sk_allocation);
  88.                 if (!skb)
  89.                     goto wait_for_memory;
  90.                 /*
  91.                  * Check whether we can use HW checksum.
  92.                  */
  93.     /*根據目的路由網路裝置的特性,確定是否設定由硬體執行校驗和的標誌。*/
  94.                 if (sk->sk_route_caps & NETIF_F_ALL_CSUM)
  95.                     skb->ip_summed = CHECKSUM_PARTIAL;
  96. /*將該skb插入到傳送佇列的尾部。*/
  97.                 skb_entail(sk, skb);
  98.     /*最後初始化copy變數為傳送資料報到網路裝置時的最大資料段的長度,copy表示每次複製到skb的資料長度。*/
  99. copy = size_goal;
  100.                 max = size_goal;
  101.             }
  102.             /* Try to append data to the end of skb. */
  103.     /*copy不能大於當前資料塊剩餘待複製的資料長度,如果大於,則需要調整copy的值。*/
  104.             if (copy > seglen)
  105.                 copy = seglen;
  106.             /* Where to copy to? */
  107.     /*判斷skb的線性儲存區底部是否還有空間。*/
  108.             if (skb_tailroom(skb) > 0) {
  109.                 /* We have some space in skb head. Superb! */
  110.               /*判斷skb的線性儲存區底部是否還有空間。如果還有,則進一步測試底部剩餘空間是否小於copy,如果是則再次調整待複製資料長度copy。
  111.                 到此為止已經計算除了本次需要複製資料的長度,接下來呼叫skb_add_data從使用者空間複製長度為copy的資料到skb中。如果複製失敗,則跳轉到do_fault處。*/
  112.                 if (copy > skb_tailroom(skb))
  113.                     copy = skb_tailroom(skb);
  114.                 if ((err = skb_add_data(skb, from, copy)) != 0)
  115.                     goto do_fault;
  116.             } else {
  117.     /*如果SKB線性儲存區底部已經沒有空間了,那就需要把資料複製到支援分散聚合的分頁中*/
  118.                 /*merge標識是否在最後一個分頁中新增資料,初始化為0*/
  119.                 int merge = 0;
  120.     /*獲取當前SKB的分片段數,在skb_shared_info中用nr_frags表示。*/
  121.                 int i = skb_shinfo(skb)->nr_frags;
  122.               /*通過巨集TCP_PAGE獲取最後一個分片的頁面page。
  123.     通過巨集TCP_OFF獲取已複製資料尾端在最後一個分片的頁面的頁內偏移。
  124. #define TCP_PAGE(sk) (sk->sk_sndmsg_page)
  125. #define TCP_OFF(sk) (sk->sk_sndmsg_off)
  126.     sk_sndmsg_page:指向為本傳輸控制塊最近一次分配的頁面,通常是當前套介面傳送佇列中最後一個SKB的分片資料的最後一頁。
  127.                 sk_sndmsg_off:表示最後一頁分片的頁內偏移,新的資料可以直接從這個位置複製到該分片中。*/
  128.                 struct page *page = TCP_PAGE(sk);
  129.                 int off = TCP_OFF(sk);
  130.                 /*呼叫skb_can_coalesce(),判斷SKB上最後一個分散聚合頁面是否有效,即能否將資料新增到該分頁上,如果可以則設定merge標誌。*/
  131.                 if (skb_can_coalesce(skb, i, page, off) &&
  132.                  off != PAGE_SIZE) {
  133.                     /* We can extend the last page
  134.                      * fragment. */
  135.                     merge = 1;
  136.                 } else if (== MAX_SKB_FRAGS || !sg) {
  137.                   /*如果不能往最後一個分片內追加資料,則需要判斷分片數量是否已達到上限,如果達到上限,則說明不能再往此SKB複製資料了,需要分配新的SKB。
  138.                     或者網路裝置不支援分散聚合I/O,則也說明不能往分片複製資料。
  139.                     在這種情況下,對當前的TCP報文設定TCPHDR_PSH標誌,並更新pushed_seq成員,表示到pushed_seq為止都是希望能儘快傳送出去的。
  140.                     最後跳轉到new_segment處,又開始分配新的SKB,因為資料還沒複製完。*/
  141.                     /* Need to add new fragment and cannot
  142.                      * do this because interface is non-SG,
  143.                      * or because all the page slots are
  144.                      * busy. */
  145.                     tcp_mark_push(tp, skb);
  146.                     goto new_segment;
  147.                 } else if (page) {
  148.     /*最後一個分頁中資料已經填滿,且分頁數量未達到上限。*/
  149.                     if (off == PAGE_SIZE) {
  150.                         put_page(page);
  151.                         TCP_PAGE(sk) = page = NULL;
  152.                         off = 0;
  153.                     }
  154.                 } else
  155.                     /*到此處只剩下一種情況了,既不能在最後一個分頁追加資料,又不能分配新的SKB,那麼不管這個SKB是否存在分頁,資料必定