歡迎您光臨本站 註冊首頁

了解 TCP 系統調用序列

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  
TCP/IP 編程介面提供各種系統調用,以幫助您有效地使用該協議。TCP 堆棧代碼數量繁多,深入到內核級別的完整調用序列可以幫助您了解 TCP 堆棧。在本文中,將回顧和學習關於 TCP 調用序列的詳細信息,其中包括對 FreeBSD 的引用,以及在用戶級進行系統調用后在 TCP 堆棧中發生的重要函數調用。

引言

典型的 TCP 客戶機和伺服器應用程序通過發布 TCP 系統調用序列來獲取某些函數。這些系統調用包括 socket ()、bind ()、listen ()、accept ()、send () 和 receive()。本文介紹在應用程序發布 TCP 系統調用時在較低級別中發生的情況,如圖 1 所示。


圖 1. TCP 應用程序進行的普通調用序列

圖 2 顯示了 TCP 系統調用在物理鏈路上發出之前進行傳播的各個層。


圖 2. TCP 系統調用的各個層

套接字層接收進行的任何 TCP 系統調用。套接字層驗證 TCP 應用程序傳遞的參數的正確性。這是一個獨立於協議 的層,因為尚未將協議連接到調用中。

套接字層下面是協議層,該層包含協議的實際實現(本例中為 TCP)。當套接字層對協議層進行調用時,將確保對兩個層之間共享的數據結構具有獨佔訪問許可權。這樣做是為了避免任何數據結構損壞。

各種網路設備驅動程序在介面層運行,該層從物理鏈路接收數據,並向物理鏈路傳輸數據。

每個套接字具有一個套接字隊列,並且每個介面具有一個用於數據通信的介面隊列。不過,對於整個協議層,只有一個稱為 IP 輸入隊列的協議隊列。介面層通過此 IP 輸入隊列將數據輸入到協議層。協議層使用相應的介面隊列將數據輸出到介面。

在本文中,將學習以下系統調用:

  • Socket
  • Bind
  • Listen
  • Accept
  • Connect
  • Shutdown
  • Close
  • Send
  • Receive

Socket

socket (struct proc *p, struct socket_args *uap, int retval)  struct sock_args   {  int domain,   int type,  int protocol;  };        

在 socket 系統調用中:

  • p 是一個指針,指向進行 socket 調用的進程的 proc 結構。
  • uap 是一個指向 socket_args 結構的指針,該結構包含傳遞到 socket 系統調用中的進程的參數。
  • retval 是系統調用的返回值。

 

socket 系統調用通過分配新的描述符創建新的套接字。將新的描述符返回到調用進程。任何後續的系統調用都使用創建的套接字標識。socket 系統調用還向創建的套接字描述符分配協議。

domain、type 和 protocol 參數值指定系列、類型和協議,以分配給創建的套接字。圖 3 顯示了調用序列。


圖 3. 用於 socket 系統調用的調用序列

從進程檢索參數后,socket 函數調用 socreate 函數。socreate 函數根據進程指定的參數發現指向協議切換 protsw 結構的指針。socreate 函數然後分配新的套接字結構。然後進行協議特定的調用 pr_usrreq,進而切換到與套接字描述符關聯的相應協議特定的請求。pr_usrreq 函數的原型為:

int  pr_usrreq(struct socket *so , int req, struct mbuf  *m0 , *m1 , *m2);         

在 pr_usrreq 函數中:
  • so 是指向套接字結構的指針。
  • req 的功能是標識請求。本例中為 PRU_ATTACH。
  • m0、m1 和 m2 是指向 mbuf 結構的指針。值因請求而異。

pr_usrreq 函數為大約 16 個請求提供服務。

tcp_usrreq() 函數調用 tcp_attach( ),以處理 PRU_ATTACH 請求。要分配 Internet 協議控制塊,可調用 in_pcballoc()。在 in_pcballoc 中,調用了內核的內存分配器函數,該函數將內存分配給 Internet 控制塊。完成所有必要的 Internet 控制塊結構指針初始化之後,該控制返回到 tcp_attach()。

分配新的 TCP 控制塊,並在 tcp_newtcpcb() 中初始化。它還初始化所有的 TCP 定時器變數,並且控制返回到 tcp_attach()。現在套接字狀態初始化為 CLOSED。在返回到 tcp_usrreq 函數時,創建套接字描述符,以指向套接字的 TCP 控制塊。

Internet 控制塊是雙向鏈接的循環鏈表,其指針指向套接字結構,同時套接字結構的 so_pcb 部分指向 Internet 控制塊結構。Internet 控制塊還具有指向 TCP 控制塊的指針。有關 Internet 控制塊和 TCP 控制塊結構的更詳細信息,請參見參考資料部分。

Bind

bind (struct proc *p, struct bind_args *uap, int *retval)     struct bind_args      {   int s;         caddr_t name;         int namelen;     };        

在 bind 系統調用函數中:

  • s 是套接字描述符。
  • name 是指向包含網路傳輸地址的緩衝區的指針。
  • namelen 是緩衝區的大小。

bind 系統調用將本地網路傳輸地址與套接字關聯。對於客戶端進程,發布 bind 調用不是強制的。當客戶端進程發布 connect 系統調用時,內核負責執行隱式綁定。伺服器進程接受連接或啟動與客戶端的通信之前,發布顯式綁定請求通常是必需的。

bind 調用將進程指定的本地地址複製到 mbuf,並調用 sobind,後者則根據請求使用 PRU_BIND 調用 tcp_usrreq()。tcp_usrreq() 中的切換實例調用 in_pcbbind(),後者將本地地址和埠號綁定到套接字。in_pcbbind 函數首先執行一些完整性檢查,以確保不綁定套接字兩次,並且至少一個介面分配了 IP 地址。in_pcbbind 負責隱式和顯式綁定。

如果對 in_pcbbind()(指向 sockaddr_in 結構的指針)的調用中的第二個參數為非空,則發生顯式綁定。其他情況下,則發生隱式綁定。對於顯式綁定,在綁定的 IP 地址上執行檢查,並相應設置套接字選項。


圖 4. 用於 bind 系統調用的調用序列

如果指定的本地埠是一個非零值,則對超級用戶特權進行檢查,以確定綁定是否位於保留的埠(例如,根據 Berkley 約定,埠號 < 1024)。然後調用 in_pcblookup(),以便查找具有提到的本地 IP 地址和本地埠號的控制塊。in_pcblookup() 驗證本地地址和埠對是否仍未使用。如果 in_pcbbind() 中的第二個參數是 NULL,或本地埠是零,則控制失敗,並檢查臨時埠(例如,根據 Berkley 約定,1024 < 埠號 < 5000)。然後調用 in_pcblookup(),以驗證發現的埠是否未使用。

Listen

listen (struct proc *p, struct listen_args *uap, int *retval)  struct listen_args  { int s;     int backlog;  };   

在 listen 系統調用中:

  • s 是套接字描述符。
  • backlog 是套接字上的連接數的隊列限制。

listen 調用指示協議,伺服器進程準備接受套接字上任何新傳入的連接。存在一個可以排列的連接數限制,在該連接數之後,忽略任何進一步的連接請求。

listen 系統調用使用套接字描述符和 listen 調用中指定的backlog 值調用 solisten。solisten 僅使用 PRU_LISTEN 作為請求調用 tcp_usrreq 函數。在 tcp_usrreq() 函數的切換語句中,PRU_LISTEN 的實例檢查套接字是否綁定到埠。如果埠為零,則調用 in_pcbbind(),將套接字綁定到一個埠(按照 Bind 部分中的描述)。

如果埠上已存在偵聽的套接字,則將套接字的狀態更改為 LISTEN。通常,所有的伺服器進程都偵聽眾所周知的埠號。很少調用 in_pcbbind 來執行伺服器進程的隱式綁定。圖 5 顯示了偵聽的調用序列。


圖 5. 用於 listen 系統調用的調用序列

Accept

accept(struct proc *p, struct accept_args *uap, int *retval);   struct  accept_args   {  	int s;  	caddr_t name;  	int *anamelen;  };        

在 accept 系統調用中:

  • s 是套接字描述符。
  • name 是緩衝區(OUT 參數),它包含外來主機的網路傳輸地址。
  • anamelen 是 name 緩衝區的大小。

accept 系統調用是等待傳入連接的阻塞調用。處理連接請求后,accept 將返回新的套接字描述符。將此新的套接字連接到客戶端,使另外一個套接字 s 保持 LISTEN 狀態,以接受進一步連接。


圖 6. 用於 accept 系統調用的調用序列

accept 調用首先驗證參數,並等待要到達的連接請求。在此之前,函數在 while 循環中阻塞。新的連接到達后,協議層喚醒伺服器進程。Accept 然後檢查函數阻塞時發生的任何套接字錯誤。如果存在任何套接字錯誤,則函數返回,並繼續從隊列拾取新的連接並調用 soaccept。在 soaccept() 中調用 tcp_usrreq () 函數,並將請求作為 PRU_ACCEPT。tcp_usrreq 函數中的切換調用 in_setpeeraddr(),後者從協議控制塊複製外來 IP 地址和外來埠號,並將其返回到伺服器進程。

Connect

connect (struct proc *p, struct connect_args *uap, int *retval);  struct connect_args   {      int s;      caddr_t name;      int namelen;  };         

在 connect 系統調用中:

  • s 是套接字描述符。
  • name 是指向具有外來 IP/埠地址對的緩衝區的指針。
  • namelen 是緩衝區的長度。

客戶端進程通常調用 connect 系統調用,以連接到伺服器進程。如果在初始化連接之前,客戶端進程沒有顯式發布 bind 系統調用,則堆棧負責本地套接字上的隱式綁定。

connect 系統調用將外來地址(需要將連接請求發送到地址)從進程複製到內核,並調用 soconnect()。從 soconnect() 返回時,connect() 函數進入睡眠狀體,直到協議層將其喚醒,並指示連接是 ESTABLISHED 或套接字上存在錯誤。soconnect() 函數檢查套接字的有效狀態,並使用 PRU_CONNECT 作為請求調用 pr_usrreq()。

tcp_usrreq() 函數中的切換實例檢查套接字與本地埠的綁定。如果未綁定套接字,則調用執行隱式綁定的 in_pcbbind()。然後調用 in_pcbconnect(),以獲取到達目的地的路線,發現必須輸出套接字的介面,並驗證 connect() 指定的外來套接字對(IP 地址和埠號)是否唯一。然後使用外來 IP 地址和埠號更新其 Internet 控制塊,並返回到 PRU_CONNECT 示例語句。

tcp_usrreq () 現在調用 soisconnecting (),它可以將客戶端主機上的套接字的狀態設置為 SYN_SENT。調用函數 tcp_output,將 SYN 包輸出到網路。控制現在返回到 connect() 函數,該函數處於睡眠狀態,直到協議層喚醒 — 指示連接現在是 ESTABLISHED,或套接字上存在錯誤。


圖 7. 用於 connect 系統調用的調用序列

3 向 TCP 握手

圖 8、圖 9 和圖 10 顯示了客戶端發布 connect 和伺服器發布 accept 以指示和建立 TCP 連接時的調用序列。


圖 8. 用於 SYN 包的流序列

當客戶端發布 connect 時,在協議層調用 tcp_output() 函數,將 SYN 包輸出到介面。如圖 9 所示,soconnect 現在返回到 connect() 函數,並進入睡眠狀態。客戶端上的套接字狀態現在是 SYN_SENT。介面層調用 if_output()(實際上是介面特定的輸出函數),將包發送到 n/w。

目的地(伺服器)上的介面接收傳入 SYN 包,將其放在 ipintrq 隊列中,並引發軟體中斷。包然後由調用 tcp_input 常式的 ipintr() 獲取。tcp_input() 在 s/w 中斷時執行,並從 ipintrq 拾取 SYN 包,對其進行處理,並將部分完成的套接字連接放入完成的套接字隊列。伺服器端的套接字狀態現在是 SYN_RCVD。每次處理后,tcp_input() 常式都調用 tcp_output()(如果需要將響應套接字發送到另一端)。


圖 9. 用於 SYN ACK 包的流序列

處理 SYN 后,伺服器使用 tcp_output ()、ip_output () 和 if_output () 序列發送 SYN ACK 包。客戶端上的 n/w 介面接收此包,將其放在 ipintrq 中,並引發 s/w 中斷。同樣,ipintr () 從 ipintrq 獲取該包,並將其傳遞到客戶端 TCP 堆棧上的 tcp_input () 常式。包現在是經過處理的,並調用了 soisconnected (),它喚醒連接調用。客戶端上的套接字狀態現在已建立。


圖 10. 用於 ACK 包的流序列

客戶端上的 tcp_input () 常式處理 SYN ACK 包,並調用 tcp_output () 將 ACK 包發回到伺服器。伺服器端上的 tcp_input () 處理此 ACK 包,並調用 soisconnected ()。此函數從未完成的套接字隊列移除套接字,並將其放入完成的套接字隊列,然後調用 Wakeup (),以喚醒 accept 調用。伺服器端的套接字現在已建立。

Shutdown

shutdown (struct proc *p, struct shutdown_args *uap, int *retval);  Struct shutdown_args  {  	int s;  	int how;  }  

在 shutdown 系統調用中:

  • s 是套接字描述符。
  • how 指定將關閉哪一部分連接。how 的值 0、1 和 2 分別指定關閉連接的讀取部分、寫入部分和同時關閉連接的讀取及寫入部分。

 

shutdown 系統調用關閉連接的任意一端或兩端。如果需要關閉讀取部分,則會丟棄接收緩衝區中存在的任何數據,並關閉該端的連接。對寫入部分,TCP 發送任何剩餘的數據,然後終止連接的寫入端。


圖 11. 用於 shutdown 系統調用的調用序列

如果需要關閉連接的讀取部分,則 soshutdown() 函數調用 sorflush()。sorflush() 標記套接字以拒絕任何傳入的包,並釋放保存的任何系統資源。

如果需要關閉連接的寫入部分,則調用 tcp_usrreq(),並將 PRU_SHUTDOWN 作為請求。PRU_SHUTDOWN 的切換實例根據當前的狀態調用 tcp_usrclosed() 函數,以更新套接字的狀態。TCP/IP 狀態圖表可以幫助了解套接字在任何給定的時間存在的不同狀態。如果從 tcp_usrclosed() 返回時需要發送 FIN,則調用 tcp_output() 將其發送到介面。

Close

soo_close(struct file *fp , struct proc *p);

在 close 系統調用中:

  • fp 是指向文件結構的指針。
  • p 是一個指向調用進程的 proc 結構的指針。

close 系統調用可關閉或中止套接字上任何掛起的連接。

soo_close() 僅調用 so_close() 函數,該函數首先檢查要關閉的套接字是否為偵聽套接字(正在接收傳入連接的套接字)。如果是,則遍歷兩個套接字隊列,以檢查任何掛起的連接。對每個掛起的連接,將調用 soabort() 以發布 tcp_usrreq(),並將 PRU_ABORT 用作請求。此切換實例調用 tcp_drop() 以檢查套接字的狀態。

如果狀態是 SYN_RCVD,則通過將狀態設置為 CLOSED 並調用 tcp_output() 發送 RST 段。tcp_close() 函數然後關閉套接字。tcp_close 函數更新路由度量結構的三個變數,然後釋放套接字持有的資源。

如果套接字不是偵聽套接字,則控制開始使用 soclose(),以檢查是否已存在附加到套接字的控制塊。如果不存在,則 sofree() 釋放套接字。如果存在,則調用具有 PRU_DETACH 的 tcp_usrreq() 將協議與套接字分離。PRU_DETACH 的切換實例調用 tcp_disconnect(),以檢查連接狀態是否為 ESTABLISHED。如果不是,則 tcp_disconnect() 調用 tcp_close(),以釋放 Internet 和控制塊。否則,tcp_disconnect() 檢查延遲時間和延遲套接字選項。如果設置了該選項,並且延遲時間為零,則調用 tcp_drop()。如果未設置,則調用 tcp_usrclosed(),以設置套接字的狀態,並調用 tcp_output()(如果需要發送 FIN 段)。

圖 12 顯示了 TCP 應用程序發布 close 系統調用時發生的重要調用。


圖 12. 用於 close 系統調用的調用序列

Send

sendmsg ( struct proc*p, struct sendmsg_args *uap, int retval);  struct sendmsg_args  {	     int s;     caddr_t msg;     int flags;  };  

在 send 系統調用中:

  • s 是套接字描述符。
  • msg 是指向 msghdr 結構的指針。
  • flags 是控制信息。

n/w 介面上有四個要發送數據的系統調用:write、writev、sendto 和 sendmsg。本文僅討論 sendmsg() 系統調用。所有的四個調用最終調用 sosend()。儘管 send(進程調用的庫函數)、sendto 和 sendmsg 系統調用僅可以對套接字描述符操作,但 write 和 writev 系統調用則可以對任何類型的描述符操作。


圖 13. 用於 sendmsg 的調用序列

sendmsg 系統調用將從進程發送的消息複製到內核空間,並調用 sendit()。在 sendit() 中,將初始化一個結構,以便從進程將輸出收集到內核中的內存緩衝區。還可以將地址和控制信息從進程複製到內核,然後調用 sosend(),以執行以下四項任務:

  • 基於 sendit() 函數傳遞的值初始化各種參數。
  • 驗證套接字的條件和連接的狀態,並確定傳遞消息和報告錯誤所需的空間。
  • 分配內存並從進程複製數據。
  • 使協議特定的調用將數據發送到網路。

 

然後調用 tcp_usrreq(),並根據進程指定的標誌,控制切換到 PRU_SEND 或 PRU_SENDOOB(以發送帶區外數據)。對於 PRU_SENDOOB,發送緩衝區大小可以超過 512 位元組,將釋放任何分配的內存並中斷控制。否則,sbappend() 和 tcp_output() 函數由 PRU_SEND 和 PRU_SENDOOB 調用。sbappend() 在發送緩衝區的末尾添加數據,並且 tcp_output() 將該段發送到介面。

Receive

recvmsg(struct proc *p, struct recvmsg_args *uap , int *retval);  struct recvmsg_args   {   int s,   struct msghdr *msg,   int flags,  };  

在 receive 系統調用中:

  • s 是套接字描述符。
  • msg 是指向 msghdr 結構的指針。
  • flags 指定控制信息。

有四個系統調用可以用於從連接接收數據:read、readv、recvfrom 和 recvmsg。儘管 recv(進程使用的庫函數)、recvfrom 和 recvmsg 僅可以對套接字描述符操作,但 read 和 readv 可以對任何種類的描述符操作。所有的 read 系統調用最終調用 soreceive()。

圖 14 顯示了用於 recvmsg 系統調用的調用序列。recvmsg() 和 recvit() 函數初始化各種數組和結構,將接收的數據從內核發送到進程。recvit() 調用 soreceive(),以便將接收的數據從套接字緩衝區傳輸到接收緩衝區進程。soreceive() 函數執行各種檢查,如:

  • 是否設置了 MSG_OOB 標誌。
  • 進程是否嘗試接收數據。
  • 是否應該阻塞,直到足夠的數據到達。
  • 將讀取數據傳輸到進程。
  • 檢查數據是帶區外數據還是常規數據,並進行相應的處理。
  • 當數據接收完成後通知協議。

 


圖 14. 用於 recvmsg 的調用序列

當設置 MSG_OOB 標誌時或數據接收完成後,soreceive() 函數進行與協議相關的請求。在接收帶區外數據的情況下,協議層檢查不同的條件,以驗證接收的數據是否為帶區外數據,然後將其返回到套接字層。在後一種情況中,協議層調用 tcp_output(),將窗口更新段發送到網路。它通知另一端任何空間都可用於接收數據。

結束語

在本文中,您學習了觸發低級別調用以完成某些任務的最重要的 TCP 函數調用。圖中的調用序列顯示了內核級 TCP 調用的簡要概述。本文是了解 FreeBSD TCP/IP 堆棧組織的很好起點。

(責任編輯:A6)



[火星人 ] 了解 TCP 系統調用序列已經有668次圍觀

http://coctec.com/docs/linux/show-post-69407.html