TCP/IP原始碼學習(23)——tcp_sendmsg(2)

nothingfinal發表於2018-03-02
本文的copyleft歸gfree.wind@gmail.com所有,使用GPL釋出,可以自由拷貝,轉載。但轉載請保持文件的完整性,註明原作者及原連結,嚴禁用於任何商業用途。
作者:gfree.wind@gmail.com
部落格:linuxfocus.blog.chinaunix.net
    

繼續前面的學習,tcp_sendmsg

  1. int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
  2.         size_t size)
  3. {
  4.     /* 
  5.     省略之前的程式碼
  6.     */
  7.     while (iovlen >= 0) {
  8.         size_t seglen = iov>iov_len;
  9.         unsigned char __user *from = iov>iov_base;
  10.         iov++;
  11.         while (seglen > 0) {

    1.           /* 
    2.           省略了之前的程式碼
    3.           */           
  12.  
  13.             /*
  14.             調整要複製的位元組數,使最多隻能複製剩下的位元組數seglen。
  15.             從後面可以看出,seglen逐漸減少
  16.             */
  17.             /* Try to append data to the end of skb. */
  18.             if (copy > seglen)
  19.                 copy = seglen;
  20.             /* Where to copy to? */
  21.             if (skb_tailroom(skb) > 0) {
  22.                 /* 如果skb還有空間,則複製到skb中去*/
  23.                 /* We have some space in skb head. */
  24.                 /*
  25.                 調整copy的大小,使其不得大於tailroom的空間。
  26.                 */
  27.                 if (copy > skb_tailroom(skb))
  28.                     copy = skb_tailroom(skb);
  29.                 /* 將資料加到skb中去*/
  30.                 if ((err = skb_add_data(skb, from, copy)) != 0)
  31.                     goto do_fault;
  32.             } else {
  33.                 /* skb中沒有空閒空間了 */
  34.                 
  35.                 /*
  36.                 這時的資料組織方式,可以參考我的另外一篇博文《tcp/ip原始碼(19)——Scatter/Gather I                 /O在L3中的應用
  37.                 */
  38.                 int merge = 0;
  39.                 int i = skb_shinfo(skb)>nr_frags;
  40.                 struct page *page = TCP_PAGE(sk);
  41.                 int off = TCP_OFF(sk);
  42.                 if (skb_can_coalesce(skb, i, page, off) &&
  43.                  off != PAGE_SIZE) {
  44.                     /* We can extend the last page
  45.                      * fragment. */
  46.                     /* 可以將資料加到最後一個page中 */
  47.                     merge = 1;
  48.                 } else if (i == MAX_SKB_FRAGS || !sg) {
  49.                     /* Need to add new fragment and cannot
  50.                      * do this because interface is nonSG,
  51.                      * or because all the page slots are
  52.                      * busy. */
  53.                     /* 
  54.                     到達此處,表明當前的page無法填充資料。
  55.                     這時,資料分片達到最大的frag數量,或者不支援Scatter Gather功能。那麼都無法繼續                     填充。這時,將tcp置為push標誌,儘快傳送資料
  56.                     */
  57.                     tcp_mark_push(tp, skb);
  58.                     /* 需要申請一個新的skb */
  59.                     goto new_segment;
  60.                 } else if (page) {
  61.                     if (off == PAGE_SIZE) {
  62.                         /* 
  63.                         之前的page已經寫滿.所以該page已經不能再繼續填充了,
  64.                         因此將sk->sk_sndmsg_page和page置為null
  65.                         */
  1.                         put_page(page);
  2.                         TCP_PAGE(sk) = page = NULL;
  3.                         off = 0;
  4.                     }
  5.                 } else 
  6.                     off = 0; //沒有可用page,所以offset為0
                  /* 調整copy的大小,使之不得大於page剩下的空間 */
  1.                 if (copy > PAGE_SIZE off)
  2.                     copy = PAGE_SIZE off;
                  /* 判斷是否需要等待,直到有許可的記憶體使用 */
  1.                 if (!sk_wmem_schedule(sk, copy))
  2.                     goto wait_for_memory;
  3.                 if (!page) {
  4.                     /* 如當前沒有page,則申請一個新的page */
  5.                     /* Allocate new cache page. */
  6.                     if (!(page = sk_stream_alloc_page(sk)))
  7.                         goto wait_for_memory;
  8.                 }
                 /* 複製資料到當前page */
  1.                 /* Time to copy data. We are close to
  2.                  * the */
  3.                 err = skb_copy_to_page(sk, from, skb, page,
  4.                          off, copy);
  5.                 if (err) {
  6.                     /* If this page was new, give it to the
  7.                      * socket so it does not get leaked.
  8.                      */
  9.                     /* 出錯了。則把這個page交給該socket,所以沒有記憶體洩露 */
  10.                     if (!TCP_PAGE(sk)) {
  11.                         TCP_PAGE(sk) = page;
  12.                         TCP_OFF(sk) = 0;
  13.                     }
  14.                     goto do_error;
  15.                 }
  16.                 /* Update the skb. */
  17.                 if (merge) {
  18.                     /* 如果是複製到已有的page上,那麼就更新對應的frags的size */
  19.                     skb_shinfo(skb)>frags[i 1].size +=
  20.                                     copy;
  21.                 } else {
  22.                     /* 這是一個新的page,那麼需要填充新的frags的值 */
  23.                     skb_fill_page_desc(skb, i, page, off, copy);
  24.                     /* 增加page的計數,如果該page沒有填滿,且sock的sk_sndmsg_page沒有值,則把當前                      page賦給它 */
  25.                     if (TCP_PAGE(sk)) {
  26.                         get_page(page);
  27.                     } else if (off + copy < PAGE_SIZE) {
  28.                         get_page(page);
  29.                         TCP_PAGE(sk) = page;
  30.                     }
  31.                 }
                 /* 調整偏移 */
  1.                 TCP_OFF(sk) = off + copy;
  2.             }
             
             /* 若沒有複製任何資料,則取消PUSH標誌 */
  1.             if (!copied)
  2.                 TCP_SKB_CB(skb)>flags &= ~TCPHDR_PSH;
             
             /* 
             調整sequence
             注意這裡的sequence number並不是tcp包中的sequence number。這裡的sequence是tcp內部使用              的
             */
  1.             tp>write_seq += copy;
  2.             TCP_SKB_CB(skb)>end_seq += copy;
  3.             skb_shinfo(skb)>gso_segs = 0;
  4.             from += copy;
  5.             copied += copy;
  6.             if ((seglen = copy) == 0 && iovlen == 0)
  7.                 goto out;
  8.             if (skb>len < max || (flags & MSG_OOB))
  9.                 continue;
  10.             if (forced_push(tp)) {
  11.                 /* 強制push, 即強制傳送資料*/
  12.                 tcp_mark_push(tp, skb);
  13.                 __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);
  14.             } else if (skb == tcp_send_head(sk)) //需要傳送該skb
  15.                 tcp_push_one(sk, mss_now)
  16.             continue;

  1. wait_for_sndbuf:
  2.             set_bit(SOCK_NOSPACE, &sk>sk_socket>flags);
  3. wait_for_memory:
  4.             if (copied)
  5.                 tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);
  6.             if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)
  7.                 goto do_error;
             /* 傳送MSS */
  1.             mss_now = tcp_send_mss(sk, &size_goal, flags);
  2.         }
  3.     }
  4. out:
  5.     if (copied)
  6.         tcp_push(sk, flags, mss_now, tp>nonagle);
  7.     TCP_CHECK_TIMER(sk);
  8.     release_sock(sk);
  9.     return copied;
     /* 下面的都是錯誤處理 */
  1. do_fault:
  2.     if (!skb>len) {
  3.         tcp_unlink_write_queue(skb, sk);
  4.         /* It is the one place in all of TCP, except connection
  5.          * reset, where we can be unlinking the send_head.
  6.          */
  7.         tcp_check_send_head(sk, skb);
  8.         sk_wmem_free_skb(sk, skb);
  9.     }
  10. do_error:
  11.     if (copied)
  12.         goto out;
  13. out_err:
  14.     err = sk_stream_error(sk, flags, err);
  15.     TCP_CHECK_TIMER(sk);
  16.     release_sock(sk);
  17.     return err;
  18. }
至此,這個函式基本上學習完畢。這裡可以看到TCP傳送資料時,buffer的組織形式,如果支援SG的話,自然是用SG。如果不支援,當多個資料傳送時,只能使用skb buffer來儲存資料。這不僅降低效率,也佔用了不必要的空間。


相關文章