TCP/IP協議棧在Linux內核中的運行時序分析

本文將要分析梳理send和recv過程當中TCP/IP協議棧相關的運行任務實體及相互協做的時序分析linux

一.  基礎概念簡介算法

1.OSI安全

  OSI(Open Systems Interconnection,開放系統互連)模型是ISO(International Organization for Standardization,國際標準化組織)設計的一種參考模型,它定義了組成網絡的各個層。該模型由7層組成,自底向上分別爲物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層和應用層。每一層都可與其緊鄰的上層和下層進行交互,而且它們都有本身的一套功能集。頂層的應用層經過應用軟件可以直接與用戶進行交互。在該模型中,每一個分層都接受由它下一層所提供的特定服務,而且負責爲本身的上一層提供特定的服務。以下圖所示:服務器

 

2.TCP/IP五層模型網絡

   TCP/IP五層協議系統,自底而上分別是物理層、數據鏈路層、網絡層、傳輸層和應用層。與OSI七層模型相似,它的每一層經過若干協議來實現不一樣的功能,且上層協議使用下層協議提供的服務。其中數據鏈路層實現了網卡接口的網絡驅動程序,以處理數據在物理媒介(好比以太網、令牌環等)上的傳輸,網絡層實現數據包的選路和轉發,傳輸層爲兩臺主機上的應用程序提供端到端(end to end)的通訊。與網絡層使用的逐跳通訊方式不一樣,傳輸層只關心通訊的起始端和目的端,而不在意數據包的中轉過程,應用層則負責處理應用程序的邏輯。它與OSI參考模型各層的對應關係以下圖所示:架構

 

 

 

3.Linux 內核網絡架構less

  Linux網絡體系結構由五個部分組成,分別是系統調用接口、協議無關接口、網絡協議、設備無關接口和設備驅動程序。系統調用接口是用戶空間的應用程序正常訪問內核的惟一合法途徑(終端和陷入也可訪問內核);協議無關接口是由socket來實現的,它提供了一組通用函數來支持各類不一樣協議; 設備無關接口是由net_device實現的,任何設備和上層通訊都是經過net_device設備無關接口;網絡棧底部則是負責管理物理網絡設備的設備驅動程序。結構分層以下圖所示:socket

 

 

二.send和recv過程分析tcp

 調用send和recv的前提是客戶端與服務器端創建鏈接。鏈接過程以下:函數

  首先,客戶端使用tcp_v4_connect發出鏈接請求,在該函數中,調用tcp_set_state函數實現發送SYN。進入tcp_set_state函數後,其調用tcp_connect(sk)函數,構造SYN的TCP頭部發送出去,並維護一個timer計時器,server端使用inet_csk_accept響應請求。此函數中維護socket請求隊列,若隊列空,則connet進入循環等待請求。取出1個請求完成3次握手,完成鏈接的創建。

  上面的tcp_connect(sk)中參數sk的類型是sock結構體,該結構的定義在linux/include/net/sock.h文件下,該結構將近兩百行的定義中包含了全部特定socket的必需狀態,包括socket使用的特定協議以及可能在其上執行的操做。

  當調用send()函數時,內核封裝send()爲sendto()而後發起系統調用。函數調用棧如圖所示:

查看具體的函數定義以下:

 由此可知,send()實際上是sendto()的一種特殊狀況,而兩者其實都是調用系統調用服務__sys_sendto()函數,該函數的參數解釋以下:

  fd       socket文件描述符

  buff     指向須要發送的數據

  len      須要發送的數據的長度

  flags    標誌位

  addr     數據報文要發送的對方端點的地址信息

  addr_len 地址信息的長度

當send()函數被調用時,參數addr被置爲NULL,addr_len爲0。查看__sys_sendto()函數的定義,如圖所示:

  __sys_sendto()首先根據傳入的描述符fd,找到對應的struct socket結構體。而後構建內核的消息結構struct msghdr,該結構定義以下:

   msg_name和msg_namelen就是數據報文要發向的對端的地址信息(即sendto系統調用中的addr和addr_len)。當使用send時,它們的值爲NULL和0。

 msg_iov表示存放待發送數據的一個緩衝區,它的定義以下:

iov_base是緩衝區的起始地址,指向message, iov_len是緩衝區的長度,指向length。msg_iovlen是緩衝區的數量,對於sendto和send來說,msg_iovlen都是1。

__sys_sendto()構建完這些後,調用sock_sendmsg()繼續執行發送流程,傳入參數爲socket和struct msghdr。sock_sendmsg()定義以下:

先執行安全檢查,以後調用sock_sendmsg_nosec()函數,定義以下:

在這裏咱們能夠看到sock->ops->sendmsg這個系統調用,它對應套接字類型的sendmsg()函數,全部的套接字類型的sendmsg()函數都是inet_sendmsg(),該函數首先檢查本地端口是否已綁定,無綁定則執行自動綁定,然後調用具體協議的sendmsg函數。

  到這裏,就進入到傳輸層了,在這裏因爲個人測試代碼使用TCP鏈接,因此調用tcp_sendmsg(),該函數定義以下:

這裏首先對sk進行加鎖,目的是讓接收和發送隊列可以有序進行相關的工做,而後主要的發送函數即爲tcp_sendmsg_locked()。這個函數代碼很是多,涉及到了tcp協議中具體的幾個處理過程。首先作一些前期的工做,例如檢查鏈接狀態和檢查數據是否分段等等。到發送這一步,能夠看到tcp_push()函數,

 該函數在判斷了是否須要設置PUSH標記位以後,會調用__tcp_push_pending_frames(),該函數調用tcp_write_xmit()完成發送。tcp_write_xmit()函數是TCP發送新數據的核心函數,包括髮送窗口判斷、擁塞控制判斷等核心操做都是在該函數中完成。大體流程是首先檢測當前狀態是不是TCP_CLOSE,而後檢測擁塞窗口的大小、檢測當前段是否徹底處在發送窗口內,再檢測段是否使用nagle算法進行發送。經過以上檢測後將SKB發送出去,最終調用的是tcp_transmit_skb(),

該函數內部調用了__tcp_transmit_skb()函數,該函數的註釋以下,

/* This routine actually transmits TCP packets queued in by
 * tcp_do_sendmsg().  This is used by both the initial
 * transmission and possible later retransmissions.
 * All SKB's seen here are completely headerless.  It is our
 * job to build the TCP header, and pass the packet down to
 * IP so it can do the same plus pass the packet off to the
 * device.
 *
 * We are working here with either a clone of the original
 * SKB, or a fresh unique copy made by the retransmit engine.
 */

可知該函數的工做是創建TCP報頭,並將數據包傳遞給IP層,在該函數的最後,咱們看到以下代碼,

 能夠看到icsk->icsk_af_ops這個指針。經過查閱相關資料,這個指針在tcp協議棧中,會被初始化爲ip_queue_xmit,因此知道了這個函數queue_xmit()在這裏進入了網絡層。因此到這個函數爲止,傳輸層發送過程追蹤完畢。當前函數調用棧以下所示:

  接下來進入IP層的分析,這一層的主要任務有路由處理、添加IP頭部、IP校驗和、IP分片和轉發進鏈路層等。

  當前函數是ip_queue_xmit(),與以前若干函數相似,該函數實際上調用__ip_queue_xmit()函數,該函數進行具體的信息處理,

 

首先skb_rtable()函數檢測skb的中的rtable是否爲空,不爲空說明已經指定了路由,跳到packet_routed繼續執行,爲空則添加,以後爲輸入包創建IP包頭, 通過本地包過濾器後,再將IP包分片輸出(ip_fragment)。

 

檢查完標誌位和路由以後,通常會調用ip_finish_output2發送數據報,最終調用dev_queue_xmit(skb)將數據包拷貝到鏈路層skb,交由下一層處理。

以上則是send過程當中,各層函數的調用過程,接下來咱們看recv函數的調用過程。

  

  與send函數相似,如圖:

 

 

 

 

首先,recv()函數是recvfrom()函數的特殊狀況,兩者實際上都調用了__sys_recvfrom()函數,recv()將後兩個參數置爲NULL,__sys_recvfrom()函數裏面調用了sock_recvmsg()函數,

 

以後調用sock_recvmsg_nosec()函數,再根據不一樣的協議類型調用不一樣的recvmsg函數,tcp調用的是 tcp_recvmsg。

 

 

以後咱們關注一下傳輸層, 傳輸層 TCP 處理入口在tcp_v4_rcv函數,數據包從IP層傳遞上來,進入該函數,查看tcp_protocol結構,

 

該結構是在af_inet.c中的inet_init()被添加的,

它的handler即爲IP層向TCP傳遞數據包的回調函數,設置爲tcp_v4_rcv。

  tcp_v4_rcv函數作了如下幾個工做:(1) 設置TCP_CB (2) 查找控制塊  (3)根據控制塊狀態作不一樣處理,包括TCP_TIME_WAIT狀態處理,TCP_NEW_SYN_RECV狀態處理,TCP_LISTEN狀態處理 (4) 接收TCP段。該函數最後調用具體的接收處理函數tcp_v4_do_rcv(),

 

 

查看源碼可知,創建鏈接後使用tcp_rcv_established()進行數據的接收,函數對頭部進行一系列檢測和相應操做,一切正常後會調用tcp_queue_rcv()函數。

 

tcp_queue_rcv函數將收到的數據掛到sk接收隊列末尾。然後然socket會被喚醒,調用system call,並最終調用 tcp_recvmsg 函數去從 socket recieve queue 中獲取 segment。

在網絡層,接收端函數的入口地址是ip_rcv,

 

 ip_rcv完成基本的校驗和處理工做後,通過PRE_ROUTING鉤子點,通過PRE_ROUTING鉤子點以後,調用ip_rcv_finish完成數據包接收,包括選項處理,路由查詢,而且根據路由決定數據包是發往本機仍是轉發。

 

ip_rcv_finish()函數會調用dst_input()函數, 在dst_input()中,最終在ip層即生成ip_input(),根據路由選擇調用ip_router_input()函數,進入路由處理環節。它首先會調用 ip_route_input 來更新路由,而後查找 route,決定該 package 將會被髮到本機仍是會被轉發仍是丟棄。數據發向上層的時,會調用 ip_local_deliver 函數,而後調用 ip_local_deliver 函數。該函數根據 package 的下一個處理層的協議號,調用下一層的包括 tcp_v4_rcv等的接口。這樣的話就能夠和咱們剛剛追蹤的傳輸層的函數鏈接起來了。

三.時序圖

 

相關文章
相關標籤/搜索