TCP/IP原始碼學習(23)——tcp_sendmsg(2)
本文的copyleft歸gfree.wind@gmail.com所有,使用GPL釋出,可以自由拷貝,轉載。但轉載請保持文件的完整性,註明原作者及原連結,嚴禁用於任何商業用途。
作者:gfree.wind@gmail.com
部落格:linuxfocus.blog.chinaunix.net
作者:gfree.wind@gmail.com
部落格:linuxfocus.blog.chinaunix.net
繼續前面的學習,tcp_sendmsg
- int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
- size_t size)
- {
- /*
- 省略之前的程式碼
- */
- while (––iovlen >= 0) {
- size_t seglen = iov–>iov_len;
- unsigned char __user *from = iov–>iov_base;
- iov++;
- while (seglen > 0) {
-
- /*
- 省略了之前的程式碼
- */
- /*
- 調整要複製的位元組數,使最多隻能複製剩下的位元組數seglen。
- 從後面可以看出,seglen逐漸減少
- */
- /* Try to append data to the end of skb. */
- if (copy > seglen)
- copy = seglen;
- /* Where to copy to? */
- if (skb_tailroom(skb) > 0) {
- /* 如果skb還有空間,則複製到skb中去*/
- /* We have some space in skb head. */
- /*
- 調整copy的大小,使其不得大於tailroom的空間。
- */
- if (copy > skb_tailroom(skb))
- copy = skb_tailroom(skb);
- /* 將資料加到skb中去*/
- if ((err = skb_add_data(skb, from, copy)) != 0)
- goto do_fault;
- } else {
- /* skb中沒有空閒空間了 */
- /*
- 這時的資料組織方式,可以參考我的另外一篇博文《tcp/ip原始碼(19)——Scatter/Gather I /O在L3中的應用》
- */
- int merge = 0;
- int i = skb_shinfo(skb)–>nr_frags;
- struct page *page = TCP_PAGE(sk);
- int off = TCP_OFF(sk);
- if (skb_can_coalesce(skb, i, page, off) &&
- off != PAGE_SIZE) {
- /* We can extend the last page
- * fragment. */
- /* 可以將資料加到最後一個page中 */
- 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. */
- /*
- 到達此處,表明當前的page無法填充資料。
- 這時,資料分片達到最大的frag數量,或者不支援Scatter Gather功能。那麼都無法繼續 填充。這時,將tcp置為push標誌,儘快傳送資料
- */
- tcp_mark_push(tp, skb);
- /* 需要申請一個新的skb */
- goto new_segment;
- } else if (page) {
- if (off == PAGE_SIZE) {
- /*
- 之前的page已經寫滿.所以該page已經不能再繼續填充了,
- 因此將sk->sk_sndmsg_page和page置為null
- */
- put_page(page);
- TCP_PAGE(sk) = page = NULL;
- off = 0;
- }
- } else
- off = 0; //沒有可用page,所以offset為0
/* 調整copy的大小,使之不得大於page剩下的空間 */
- if (copy > PAGE_SIZE – off)
- copy = PAGE_SIZE – off;
/* 判斷是否需要等待,直到有許可的記憶體使用 */
- if (!sk_wmem_schedule(sk, copy))
- goto wait_for_memory;
- if (!page) {
- /* 如當前沒有page,則申請一個新的page */
- /* Allocate new cache page. */
- if (!(page = sk_stream_alloc_page(sk)))
- goto wait_for_memory;
- }
/* 複製資料到當前page */
- /* Time to copy data. We are close to
- * the */
- 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.
- */
- /* 出錯了。則把這個page交給該socket,所以沒有記憶體洩露 */
- if (!TCP_PAGE(sk)) {
- TCP_PAGE(sk) = page;
- TCP_OFF(sk) = 0;
- }
- goto do_error;
- }
- /* Update the skb. */
- if (merge) {
- /* 如果是複製到已有的page上,那麼就更新對應的frags的size */
- skb_shinfo(skb)–>frags[i – 1].size +=
- copy;
- } else {
- /* 這是一個新的page,那麼需要填充新的frags的值 */
- skb_fill_page_desc(skb, i, page, off, copy);
- /* 增加page的計數,如果該page沒有填滿,且sock的sk_sndmsg_page沒有值,則把當前 page賦給它 */
- 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;
- }
/* 若沒有複製任何資料,則取消PUSH標誌 */
- if (!copied)
- TCP_SKB_CB(skb)–>flags &= ~TCPHDR_PSH;
/*
調整sequence
注意這裡的sequence number並不是tcp包中的sequence number。這裡的sequence是tcp內部使用 的
*/
- 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)) {
- /* 強制push, 即強制傳送資料*/
- tcp_mark_push(tp, skb);
- __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);
- } else if (skb == tcp_send_head(sk)) //需要傳送該skb
- 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 */
- 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);
- 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;
- }
至此,這個函式基本上學習完畢。這裡可以看到TCP傳送資料時,buffer的組織形式,如果支援SG的話,自然是用SG。如果不支援,當多個資料傳送時,只能使用skb buffer來儲存資料。這不僅降低效率,也佔用了不必要的空間。
想了解SG用法的同學,可以參考《tcp/ip原始碼(19)——Scatter/Gather I/O在L3中的應用》,地址是http://blog.chinaunix.net/space.php?uid=23629988&do=blog&id=196823。
相關文章
- tcp/ip 學習(一)TCP
- 【TCP/IP】TCP伺服器併發處理&原始碼TCP伺服器原始碼
- Java集合原始碼學習(2)ArrayListJava原始碼
- Koa2 原始碼學習(下)原始碼
- Koa2 原始碼學習(上)原始碼
- Spring原始碼學習之:ClassLoader學習(2)Spring原始碼
- 1.2.3_2 TCP/IP模型TCP模型
- 學習心得 TCP/IP攻擊原理分析總結TCP
- TCP/IP學習筆記之協議和郵件TCP筆記協議
- UiAutomator原始碼學習(2)-- UiAutomationBridgeUI原始碼
- 學習JUC原始碼(2)——自定義同步元件原始碼元件
- TCP學習TCP
- vue原始碼學習筆記2(resolveConstructorOptions)Vue原始碼筆記Struct
- vue原始碼學習Vue原始碼
- MMKV原始碼學習原始碼
- EventBus原始碼學習原始碼
- fishhook原始碼學習Hook原始碼
- 學習HashMap原始碼HashMap原始碼
- koa原始碼學習原始碼
- express原始碼學習Express原始碼
- redis原始碼學習Redis原始碼
- Ember原始碼學習原始碼
- go原始碼學習Go原始碼
- TCP學習指北TCP
- oracle OGG-01232 Receive TCP params error:TCP/IP error 232(connection reset)OracleTCPError
- Ruby 2.x 原始碼學習:直譯器概述原始碼
- Java容器原始碼學習--ArrayList原始碼分析Java原始碼
- 07.ElementUI 2.X 原始碼學習:原始碼剖析之工程化(二)UI原始碼
- 08.ElementUI 2.X 原始碼學習:原始碼剖析之工程化(三)UI原始碼
- 06.ElementUI 2.X 原始碼學習:原始碼剖析之工程化(一)UI原始碼
- PHP 原始碼加密學習PHP原始碼加密
- Okio 框架原始碼學習框架原始碼
- Vue 原始碼學習(一)Vue原始碼
- Masonry 原始碼學習整理原始碼
- 精讀《原始碼學習》原始碼
- java原始碼學習-SpliteratorJava原始碼
- jQuery原始碼學習之$()jQuery原始碼
- Mybatis 原始碼學習(二)MyBatis原始碼