歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Unix知識 >> 關於Unix

Linux核心--11.網絡

第十章 網絡 網絡 和Linux是密切相關的。從某種意義來說Linux是一個針對Inte .net 和WWW的產品。它的 開發 者和用戶用Web來交換信息思想、程序代碼,而Linux自身常常被用來支持各種組織機構的網絡 需求。這一章講的是Linux如何支持如TCP/IP等網絡 協議的。 T 第十章 
網絡

    網絡和Linux是密切相關的。從某種意義來說Linux是一個針對Inte.net和WWW的產品。它的開發者和用戶用Web來交換信息思想、程序代碼,而Linux自身常常被用來支持各種組織機構的網絡需求。這一章講的是Linux如何支持如TCP/IP等網絡協議的。 

    TCP/IP協議最初是為支持ARPANET(一個美國政府資助的研究性網絡)上計算機通訊而設計的。ARPANET 提出了一些網絡概念如包交換和協議分層(一個協議使用另一個協議提供的服務)。ARPANET於1988年隱退,但是它的繼承人(NSF1 NET和Internet)卻變得更大了。現在我們所熟知的萬維網World Wide Web就是從ARPANET演變過來的,它自身支持TCP/IP協議。Unix TM 被廣泛應用於ARPANET,它的第一個網絡版本是4.3 BSD。Linux的網絡實現是以4.3 BSD為模型的,它支持BSD sockets(及一些擴展)和所有的TCP/IP網絡。選這 個編程接口是因為它很流行,並且有助於應用程序從Linux平台移植到其它Unix TM 平台。 

10.1  TCP/IP網絡簡介
    這一部分簡單介紹一下TCP/IP網絡的主要原理,而不是進行詳細地講述。在IP網絡中,每台機器都有一個 IP 地址,一個32位的數字,它唯一地標識這台機器。WWW是一個非常巨大並且迅速增長的網絡,每台連在上面的機器都必須有一個獨立的IP地址。IP地址由四個用點分開的數字表示,如16.42.0.9。這個IP地址實際上分成兩個部分:網絡地址和主機地址,每部分的長度是可以變化的(有好幾類IP地址)。以16.42.0.9為例,網絡地址是16.42,主機地址是0.9。主機地址又進一步分為子網地址和主機地址。還是以16.42.0.9為例,子網地址是16.42.0,主機地址是16.42.0.9。這樣的子劃分可以允許某部門劃分他們自己的子網絡。例如,如果16.42是ACME計算機公司的網絡地址,則16.42.0可能是子網0,16.42.1可能是子網1。 這些子網可以是分別建立的,可能租用電話線或用微波進行相互間通訊。IP地址由網絡管理員分配,用IP 子網可以很好地管理網絡。IP子網的管理員可以自由分配子網內的IP地址。 
    通常,IP地址是比較難記的,而名稱則容易多了,象linux.acme.com就比16.42.0.9要好記一些。但是必須有一些機器來將網絡名稱轉變為IP地址。這些名稱被靜態地定義在 /etc/hosts 文件中或者Linux能請求域名服務器(DNS)來解析它。這種情況下,本地主機必須知道一個或一個以上的DNS服務器並且這些服務器要將其名稱指定到 /etc/resolv.conf 中。 

    當你想要與另一台計算機連接時,比如說你想閱讀一個Web頁,你的IP地址就會被用來與那台機器交換數據。這些數據被包含在一些IP包中,每個IP包都有一個IP頭用來包含源機器的IP地址和目的機器的IP地址,校驗和以及其它的有用信息。IP包的校驗和用來讓IP包的接收端判斷IP包是否在傳輸過程中發生錯誤,譬如說由於電話線路的問題而引起的錯誤。應用程序想要傳輸的數據可能被分成很多個容易處理的小包。IP數據包的大小是根據傳輸媒體的變化而不同的;以太網包通常比PPP包要大一些。目的主機在將數據送給接收端應用程序前需要將這些包重新拚裝起來。如果你從一個比較慢的站點訪問一個有大量圖象的Web頁,就會看到數據的分割與重組。 

    同一子網內的主機之間可以直接發送IP包,而其它的IP包將被送到一個特定的主機:網關。網關(或路由器)是用來連接多個IP子網的,它們會轉發送從子網內來的IP包。例如,如果子網16.42.1.0和16.42.0.0之間通過一個網關相連,那麼任何從子網0發往子網1的包必須由網關指引,網關可以幫這些包找到正確的路線。本地主機建立路由表用以為IP包找到正確的機器。每一個目的IP都有一個條目在路由表中,用以告訴Linux將IP包送到哪一台主機。這些路由表是隨網絡的拓撲結構變化而動態變化的。 






圖 10.1: TCP/IP 協議層 

    IP協議是一個傳輸層的協議,其它協議可以用它來傳輸數據。傳輸控制協議(TCP)是一個可靠的端對端的協議,它用IP來傳送和接收它自己的包。正如IP包有它自己的頭一樣,TCP也有它自己的頭。TCP是一個面向連接的協議,兩個網絡應用程序通過一個虛連接相連,即使它們之間可能隔著很多子網、網關、路由器。TCP可靠地傳送和接收兩應用程序間的數據,並保證數據不會丟失。當用IP來傳輸TCP包時,IP包的數據段就是TCP包。每一個通訊主機的IP層負責傳送和接收IP包。用戶數據報協議(UDP)也用IP層來傳輸它的包,不象TCP,UDP不是一個可靠的協議,但它提供了一種數據報服務。有多個協議可以使用IP層,接收IP包的時候必需知道該IP包中的數據是哪個上層協議的,因此IP包頭中有個一字節包含著協議標識符。例如,當TCP請求IP層傳輸一個IP包時,IP包的包頭中用標識符指明該包包含一個TCP包,IP接收層用該標識符決定由哪一協議來 接收數據,這個例子中是TCP層。當應用程序通過TCP/IP進行通訊時,它們不僅要指定目標的IP地址,而且還 要指定應用的端口地址。一個端口地址唯一地標識一個應用,標准的網絡應用使用標准的端口地址;如,Web 服務使用80端口。這些已登記的端口地址可在 /etc/services 中看到。 

    這一層的協議不僅僅是TCP、UDP和IP。IP協議層本身用很多種物理媒介將IP包從一個主機傳到其它主機。這些媒介可以加入它們自己的協議頭。以太網層就是一個例子,但PPP和SLIP不是這樣。一個以太網絡允許很個主機同時連接到同一根物理電纜。傳輸中的每一個以太網幀可以被所有主機看見,因此每一以太網設備有個唯一的地址。任何傳送給該地址的以太網幀被有該地址的以太網設備接收,而其它主機則忽略該幀。這個唯一的地址內置於每一以太網設備中,通常是在網卡出廠時就寫在SROM2中了。以太網地址有6個字節長,如: 08-00-2b-00-49-A4 。一些以太網地址是保留給多點傳送用的,送往這些地址的以太網幀將被網上所有的主機接收。以太網幀可以攜帶很多種協議(作為數據),如IP包,並且也包括它們頭中的協議標識符。這使得以太網層能正確地接收IP包並將它們傳給IP層。 

    為了能通過象以太網這樣的多連接協議傳送IP包,IP層必須找到每一IP主機的以太網地址。IP地址僅僅是一個地址概念,以太網設備有它們自身的物理地址。從另一方面說,IP地址是可以被網絡管理員根據需要來分配和再分配的,而網絡硬件只對含有它們自己的物理地址或多點傳送地址的以太網幀作出響應。Linux用地址 解析協議(ARP)來允許機器將IP地址轉變成真正的硬件地址,如以太網地址。如果一個主機想知道某一IP地址對應的硬件地址,它就用一個多點傳送地址將一個包含了該IP地址的ARP請求包發給網上所有節點,擁有該IP地址的的目標主機則響應一個包含物理硬件地址的ARP應答。ARP不僅僅局限於以太網設備,它能夠用來在其它一些物理媒介上解析IP地址,如FDDI。那些不支持ARP的網絡設備會被標記出來,Linux將不會用ARP。還有一個提供相反功能的反向地址解析協議(RARP),用來將物理網絡地址轉變為IP地址。這一協議常常被網關用來響應包含遠程網絡IP地址的ARP請求。 

10.2  Linux TCP/IP 網絡層





圖 10.2: Linux 網絡層 

    正如網絡協議本身,圖 10.2 顯示出Linux用一系列相互連接層的軟件實現Internet協議地址族。BSD套接字(BSD sockets)由專門處理BSD sockets通用套接字管理軟件處理。它由INET sockets層來支持,這一層為基於IP的協議TCP和UDP管理傳輸端點。UDP(用戶數據報協議)是一個無連接協議而TCP(傳輸控制協議)是個可靠的端對端協議。傳輸UDP包時,Linux不知道也不關心是否它們安全到達目的地。TCP包則被TCP連接兩端編號以保證傳輸的數據被正確接收。IP層包含了實現Internet協議的代碼。這些代碼給要傳輸的數據加上IP頭,並知道如何把傳入的IP包送給TCP或UDP。在IP層以下,是網絡設備來支持所有Linux網絡工作,如PPP和以太網。網絡設備不總是物理設備;一些象loopback這樣的設備是純軟件設備。標准的Linux設備用mknod命令建立,網絡設備要用底層軟件發現並初始化它。建立一個有適當的以太網設備驅動在內的內核後,你就可以看到 /dev/eth0 。ARP協議位於IP層與支持ARP的協議之間。 


10.3  BSD Socket 接口
    這是一個通用的接口,它不僅支持各種網絡工作形式,而且還是一個交互式通訊機制。一個套接字描述一個通訊連接的一端,兩個通訊程序中各自有一個套接字來描述它們自己那一端。套接字可以被看成一個專門的管道,但又不象管道,套接字對它們能容納的數據量沒有限制。Linux支持多種類型的套接字。這是因為每一類型的套接字有它自己的通信尋址方法。Linux支持下列套接字地址族或域: 

  UNIX  Unix 域套接字  
INET  Internet地址族支持通過TCP/IP協議的通信  
AX25  Amateur radio X25  
IPX  Novell IPX  
APPLETALK  Appletalk DDP  
X25  X25  


    有一些套接字類型支持面向連接的服務類型。並非所有的地址族能支持所有的服務類型。Linux BSD 套接字支持下列套接字類型: 


Stream 
這些套接字提供可靠的雙工順序數據流,能保證傳送過程中數據不丟失,不被弄混和復制。Internet地址中的TCP協議支持流套接字。 
Datagram 
這些套接字提供雙工數據傳送,但與流套接字不同,這裡不保證信息的到達。即使它們到達了,也不能保其到達的順序,甚至不能保證被復制和弄混。這類套接字由Internet地址族中的UDP協議支持。 
Raw 
允許直接處理下層協議(所以叫“Raw”)。例如,有可能打開一個raw套接字到以太網設備,看 raw IP數據傳輸。 
Reliable Delivered Messages 
與數據報很象,但它能保證數據的到達。 
Sequenced Packets 
與流套接字相似,但的數據包大小是固定的。 
Packet 
這不是一個標准的BSD套接字類型,而是一個Linux特定的擴展,它允許在設備級上直接處理包。 
    客戶服務器模式下使用套接字進行通信。服務器提供一種服務,客戶使用這種服務。Web服務器就是一個例子,它提供網頁,而客戶端,或者說浏覽器,來讀這些網頁。服務器要使用套接字,首先要建立套接字並將它與一個名稱綁定。名稱的格式由套接字的地址族來定,是服務器的本地有效地址。套接字的名稱或地址用結構sockaddr來指定。一個INET套接字還與一個端口地址綁定。已注冊的端口號可在 /etc/services 中找到;例如,Web服務的端口號是80。將套接字與地址綁定以後,服務器不可以監聽指定的綁定了的地址上的引入連接請求。請求的發起者,客戶端,建立一個套接字並通過它來發出一個連接請求到指定的目標服務器地址。對於一個INET套接字,服務器地址是它的IP地址和它的端口號。這些引入請求必須通過各種協議層找到目的地址,然後等待服務器的監聽套接字。服務器收到引入請求後可以接收或拒絕它。如果決定接收,服務器必需建立一個新一套接字來接收請求。當一個套接字被用來監聽引入連接請求時,它就不能用來支持連接了。連接建立後兩端就可以自由地發送和接收數據了。最後,當不再需要連接時,就將之關閉。要注意保證在傳輸過程正確處理數據包。 

    對BSD socket進行准確操作要依賴於它下面的地址族。設置TCP/IP連接與設置amateur radio X.25連接有很大不同。象虛擬文件系統一樣,Linux從BSD socket層抽象出socket接口,應用程序和BSD socket由每個地址族的特定軟件來支持。內核初始化時,地址族被置入內核中並將自己注冊到BSD socket接口。之後,當應用程序建立用使用BDS sockets時,在BSD socket與它支持的地址族之間將產生一個聯接。這一聯接是由交叉鏈接數據結構和地址族表特定支持程序產生。例如,每當應用程序建立一個新的socket,就會有一個BSD socket接口用的地址族特定 socket建立程序。 

    構造內核時,一些地址族和協議被置入 protocols 向量。每個由它的名稱來表征,例如,“INET”和它的初始程序地址。當套接口啟動時被初始化時,要調用每一協議和初始程序。對socket地址族來說,這會導致它們注冊一套協議操作。這是一套例程,其中的每一例程執行一個特定的針對那一地址族的操作。已注冊的協議操作被存在 pops 向量,一個指向 proto_ops 數據結構的向量中。 

    proto_ops 結構由地址族類型和一系列指向與特定地址族對應的socket操作例程的指針組成。pops 向量通過地址族標識符來索引,如Internet地址族標識符(AF_INET是2)。 





圖 10.3: Linux BSD Socket 數據結構 


10.4  INET Socket 層
    INET socket層支持包括TCP/IP協議在內的internet地址族。如前所述,這些協議是分層的,一個協議使用另一個協議的服務。Linux的TCP/IP代碼和數據結構反映了這一分層模型。它與BSD socket層的接口要通過一系列Internet地址族socket操作,這一操作是在網絡初始化時就已經注冊到BSD socket層的。這些都與其它已注冊的地址族一起保存在 pops 向量中。BSD socket層從已注冊的INET proto_ops 數據結構中調用INET層 socket支持例程來為它執行工作。例如,一個地址族為INET的BSD socket建立請求,將用到下層的INET socket的建立函數。在這些操作中,BSD socket層把用來描述BSD socket的 socket 結構傳構到INET層。為了不把BSD socket 與TCP/IP的特定信息搞混,INET socket層使用它自己的數據結構,sock ,它與BSD socket 結構相連。這一聯接關系可以從圖  10.3 中看出。它用BSD socket的 data 指針來連接 sock 結構與BSD socket結構。這意味著後來的INET socket調用能夠很容易地重新找到 sock 結構。 sock 結構的協議操作指針也在初始化時建立,它依賴與被請求的協議。如果請求的是TCP,那麼 sock 結構的協議操作指針將指向TCP連接所必需的TCP協議操作集。 


10.4.1  建立BSD socket
    系統建立一個新的socket時,通過標識符來確定它的地址族,socket類型和協議。 

    首先,從 pops 向量中搜索與被請求的地址族相匹配的地址族。它可能是一個作為核心模塊來實現的一個特定的地址族,這樣,在其能繼續工作前, kerneld 守護進程必須加載這一模塊。分配一個新的 socket 結構來代表BSD socket。實際上 socket 結構是 VFS inode 結構的一部分,分配一個socket實際上就是分配一個 VFS inode 。除非你認為socket操作能和普通的文件操作一樣,否則會覺得這好象很奇怪。所有的文件用VFS inode結構來表示,為了支持文件操作,BSD socket必須也用 VFS inode 來表示。 

    最新建立的 BSD socket 結構包含一個指向地址族特定socket例程的指針,可以用來從 pops 向量中找到 proto_ops 結構。它的類型被設置成被請求的socket類型:SOCK_STREAM,SOCK_DGRAM等等之一。調用地址族特定創建例程使用保存在 proto_ops 結構中的地址。 

    從當前過程 fd 向量中分配一個自由的文件描述符,對 file 結構所指向的進行初始化。包括將文件操作指針設置為指向由BSD socket接口支持的BSD socket文件操作集。任何操作將被引到socket接口,通過調用它的地址族操作例程將它們傳到支持的地址族。 

10.4.2  將地址與INET BSD socket綁定
    為了能監聽輸入的internet連接請求,每個服務器必須建立一個INET BSD socket,並將地址與其綁定。綁定操作主要在INET socket層內處理,下面的TCP和UDP協議層提供一些支持。與一個地址綁定了的socket不能用來進行任何其它的通訊工作,也就是說:socket的狀態必須是 TCP_CLOSE 。 sockaddr 結構包含了與一個任意的端口號綁定的IP地址。通常綁定的IP地址已經分配給了一個網絡設備,該設備支持INET地址族且其接口是可用的。可以在系統中用ifconfig命令來查看哪一個網絡接口是當前激活的。IP地址也可以是廣播地址,全1或全0。這是些特定的地址,用以表示發送給任何人3。如果機器充當一個透明的代理或防火牆,則IP地址可被指定為任一個IP地址,但只有有超級用戶權限的進程能綁定到任何一個IP地址。綁定的IP地址被存在sock結構中的 recv_addr 和 saddr 字段。端口號是可選的,如果沒有指定,將任意指定一個。按慣例,小於1024的端口號不能被沒有超級用戶權限的進程使用。如果下層網絡沒有分配端口號,則分配一個大於1024的端口號。 

    下層網絡設備接收的包必須由經正確的INET和BSD socket才能被處理。因此,UDP和TCP維護了一些hash表用來在輸入IP消息內查找地址並將它們導向正確的 socket/sock 對。TCP是一個面向連接的協議,因而涉及處理TCP包的信息比用於處理UDP包的信息多。 

UDP維護著一張已分配UDP端口表, udp_hash 表。由指向 sock 數據結構的指針組成,通過一個基於端口號的hash函數來索引。UDPhash表比允許的端口號的數目小得多(udp_hash 為128 或者說是 UDP_HTABLE_SIZE )表中的一些項指向一個 sock 結構鏈,該鏈用每個 sock 結構中的 next 指針來將每個 sock 連接起來。 

    TCP是十分復雜的,它包括幾個hash表。但實際上TCP在綁定操作時沒有將 sock 結構與其hash表綁定,它僅僅檢查被請求的端口號當前沒被使用。 sock 結構是在 listen 操作時被加入TCP的hash表的。 

復習提要: What about the route entered? 


10.4.3  在INET BSD Socket上建立連接
    建立一個socket,如果沒有用它來監聽連入請求,那麼就能用它來發連出請求。對於面向無連接的協議如UDP來說,這一socket操作並不做許多事,但對於面向連接的協議如TCP來說,這一操作包括了在兩個應用間建立一個虛連接。 
    一個連出連接操作只能由一個在正確狀態下的INET BSD socket來完成;換句話說,socket不能是已建立連接的,並且有被用來監聽連入連接。這意味著BSD socket 結構必須是 SS_UNCONNECTED 狀態。UDP協議沒有在兩個應用間建立虛連接,任何發出的消 息都是數據報,這些消息可能到達也可能不到達目的地。但它不支持BSD socket的 connect 操作。建立在UDP的INET BSD socket上的連接操作簡單地設置遠程應用的地址:IP地址和IP端口號。另外,它還設置路由表入口的cache以便這一BSD socket在發用UDP包時不用再次查詢路由數據庫(除非這一路由已經無效)。INET sock 結構中的 ip_route_cache 指針指向路由緩存信息。如果沒有給出地址信息,緩存的路由和IP地址信息將自動地被用來發送消息。UDP將 sock 的狀態改為 TCP_ESTABLISHED 。 

對於基於TCP BSD socket的連接操作,TCP必須建立一個包括連接信息的TCP消息,並將它送到目的IP。TCP消息包含與連接有關的信息,一個唯一標識的消息開始順序號,通過初始化主機來管理的消息大小的最大值,及發送與接收窗口大小等等。在TCP內,所有的消息都是編號的,初始的順序號被用來作為第一消息號。Linux選用一個合理的隨機值來避免惡意協議沖突。每一從TCP連接的一端成功地傳到另一端的消息要確認其已經正確到達。未確認的消息將被重傳。發送與接收窗口的大小是第一個確認到達之前消息的個數。消息尺寸的最大值與網絡設備有關,它們在初始化請求的最後時刻確定下來。如果接收端的網絡設備的消息尺寸最大值更小,則連接將以小的一端為准。應用程序發出連接請求後必須等待目標應用程序的接受或拒絕連接的響應。TCP sock 期望著一個輸入消息,它被加入 tcp_listening_hash 以便輸入TCP消息能被指向這一 sock 結構。TCP同時也開始計時,當目標應用沒有響應請求,則連出連接請求超時。 

10.4.4  監聽 INET BSD Socket
    socket與地址綁定後,能監聽指定地址的連入連接請求。一個網絡應用程序能監聽socket而不用先將地址 與之綁定;在這個例子中,INET socket層找到一個未用的端口號(對這一協議)並自動將它與socket綁定。 監聽socket函數將socket狀態設成 TCP_LISTEN ,並做其它連入連接所需要的工作。

    對於UDP sockets,改變socket的狀態就足夠了,而TCP現在加了socket的 sock 數據結構到兩個hash表中並激活, tcp_bound_hash 表和 tcp_listening_hash 表。這兩個表都通過一個基於IP端口號的hash函數來索引。

    無論何時,一個激活的監聽socket接收一個連入的TCP連接請求,TCP都要建立一個新的 sock 結構來描述它。最終接收時,這個 sock 結構將成為TCP連接的底層。它也復制包含連接請求的 sk_buff ,並將它放到監聽 sock 結構的 receive_queue 中排隊。復制的 sk_buff 包含一個指向新建立的 sock 結構的指針。 

10.4.5  接收連接請求
    UDP不支持連接的概念,接收INET socket連接請求只適用於TCP協議,一個監聽socket接收操作從原始的監聽socket中復制新的socket結構。接收操作透過支持的協議層,本例是INET,來接收任何連入連接請求。如果下層協議,如UDP,不支持連接,INET協議層接收操作將失敗。否則接收操作透過真實協議層,本例是TCP。接收操作可以是阻塞或非阻塞。在非阻塞情況下,如果沒有連入連接可接收,則接收操作失敗,新建的socket 結構被廢棄。在阻塞情況下,網絡應用程序執行接收操作將加上一個等待隊列並將之掛起,直到接收到TCP連接請求。當接收一個連接請求後,包含請求的 sk_buff 被廢棄,並且 sock 數據結構返回到INET socket層,在那與一個新的更早建立的socket結構連接。新socket文件描述符(fd)號返回給網絡應用程序,然後,應用程序就能在socket操作中將這一文件描述符用於新建立的INET BSD socket。 

10.5  IP層
10.5.1  Socket 緩存
    每一層協議用另外層提供的服務,這樣使用多層網絡協議會有一個問題:每個協議都要在傳送數據時都要 加上協議頭和協議尾,而數據到達時又要將之去掉。這樣,在不同的協議間要有數據緩存,每一層需要知道特 定協議的頭和尾放在哪個位置。一個解決辦法就是在每一層中都拷貝緩存,但這樣做效率就很低。Linux用 socket緩存或者說 sk_buffs 來在協議層與網絡設備驅動之間交換數據。 sk_buffs 包括指針和字段長度,這樣每 個協議層就可以通過標准的函數或“方法”來操作應用程序數據。 





圖 10.4: Socket 緩存 (sk_buff) 

    圖  10.4 顯示了 sk_buff 數據結構;每個 sk_buff 有一個數據塊與之相連。 sk_buff 有四個指針,這些指針 用來操作和管理socket緩存的數據: 

head 
指向內存中數據區的開頭。這一指針在 sk_buff 和其相關的數據塊分配時就固定了。 
data 
指向當前協議數據的開頭。這一指針是隨當前擁有 sk_buff 的是哪個協議層而變化的。 
tail 
指向當前協議數據的結尾。同樣,這一指針也是隨當前擁有 sk_buff 的是哪個協議層而變化的。 
end 
指向內存中數據區的結尾。這一指針在 sk_buff 和其相關的數據塊分配時固定。 
     len 和 truesize 這兩個字段分別用來描述當前協議包長度和數據緩存總體長度。 sk_buff 處理代碼提供標准的操作來向應用程序增加和移除協議頭和協議尾。這就可以安全地操作 sk_buff 中的 data , tail 和 len 字段。 

push 
它把 data 指針指向數據區的開始並增加 len 。用於在要傳輸的數據開始處增加協議頭。 

pull 
它把 data 指針從數據區的開始處移到數據區的結尾處,並減小 len 。用於在已接收的數據開始處移除協議頭。 

put 
它把 tail 指針指向數據區的結尾處,並增加 len 。用於在要傳輸的數據結尾處增加數據或協議信息。 

trim 
它把 tail 指針指向數據區的開始處,並減小 len 。用於在已接收的數據尾移除數據或協議信息。 

    sk_buff 結構還包含了用於一些指針,用於在處理過程中存入 sk_buff 的雙連接環路列表。通用sk_buff例 程可以將 sk_buff 加入到這些列表的前面或後面,也可以刪除它們。 

10.5.2  接收IP包
    第  dd-chapter 章描述了Linux的網絡設備是如何置入內核並初始化的。一系列 device 數據結構在 dev_base 表中相互連接起來。每個 device 結構描述了它的設備並提供回調例程,當需要網絡驅動來執行工作時,網絡協議層調用這些例程。這些函數與傳輸的數據及網絡設備地址緊密相關。當一個網絡設備從網上接收包時,它必須將接收的數據轉換成 sk_buff 結構。這些 sk_buff 則被網絡驅動加入到了 backlog 隊列中。

    如果 backlog 隊列太長,則丟棄接收的 sk_buff。准備好要運行時,網絡底層將被設置標志。

    當網絡底層按計劃開始運行後,處理 backlog 隊列之前,任何等待著被傳輸的網絡包都由它來處理。 sk_buff 決定哪些層處理被接收的包。 

    Linux網絡層初始化時,每一協議通過將 packet_type 結構加入到 ptype_all列表或ptype_base hash表中來 注冊它自己。packet_type 結構包含了協議類型,一個指向網絡設備的指針,一個指向協議的接收數據處理例程的指針,最後還包括一個指向列表鏈或hash鏈中下一個 packet_type 結構的指針。ptype_all 鏈用於監聽從網絡設備上接收的所有包,通常不使用它。ptype_base hash表是被協議標識符弄亂的,用於決定哪個協議將接收傳入的網絡包。網絡底層通過兩個表中的一個或多個 packet_type 項來匹配傳入 sk_buff 的協議類型。協議可以和多於一個的項相匹配,如在監聽網上所有的傳輸時要復制多個 sk_buff 。 sk_buff 將通過被匹配協議處理例程。 

10.5.3  發送IP包
    應用程序交換數據時要傳輸包,否則由網絡協議在建立連接或支持一個已建立的連接時來生成。無論數據 是由哪種方法生成的,都要建立一個 sk_buff 來包含數據,當通過協議層時,這些協議層會加上各種頭。

    sk_buff 需要通過網絡設備傳輸。首先協議,如IP,需要確定是哪個網絡設備在用。這有賴於包的最佳路 由。對於通過modem連入一個簡單網絡,如通過PPP協議,的計算機來說,路由的選擇是很簡單的。包應該通過 本地環路設備發送給本地主機,或發送給PPP modem連接的網關。對於連在以在網上的計算機來說,連接在網 絡上的計算機越多,路由越復雜。

    對於每一個被傳輸的IP包,IP用路由表來為目的IP地址解析路由。從路由表中成功地找到目的IP時將返回一個描述了要使用的路由的 rtable 結構。這包括要用到的源IP地址,網絡 device 結構的地址,有時還有預建立的硬件頭。這些硬件頭是網絡設備特定的,包含了源和目的的物理地址和其它的特定媒體信息。如果網絡設備是一個以太網設備,硬件頭則應如圖  10.1 所示,並且源和目的地址應是物理的以太網地址。硬件頭在路由的時候會緩存起來,因為必須將它加到每一個要傳輸的IP包中。硬件頭包含的物理地址要用ARP協議來解析。傳出的包在地址被解析後才會發出。解析了地址後,硬件頭被緩存起來以便以後的IP包在使用這一接口時不需要再使用ARP。

10.5.4  數據分塊

    每個網絡設備都有一個包大小的最大值,發送或接收數據包不能比這一值大。IP協議允許將數據分成更小單元以便網絡設備能處理。IP協議頭有分塊字段,它裡面包含了一個標志和分割偏移量。 

    當IP包准備要傳輸時,IP找到網絡設備來將IP包發送出去。這個設備是從IP路由表中找到的。每一 device 結構中有一項 mtu ,用來描述最大傳輸單元(以字節為單位)。如果設備的 mtu 比要傳輸的IP包的包大小要小,則IP包必須被分割成更小的單元。每一單元用一個 sk_buff 結構來表征;它的IP頭會被做上標記以標識它是一個分塊了的包,其中還包含分割偏移量。最後一個包被標識為最後IP單元。如果在分塊過程中,IP不能分配 sk_buff ,則傳輸失敗。 

    接收IP分塊單元要比發送它們要麻煩一些,因為這些IP單元可能以任何順序到達,必須所有的單元都接收 到了以後才能重新將它們組裝起來。每接收一個IP包都要檢查其是否是IP分割單元。在第一個IP分割單元到達 時,IP會建立一個新的 ipq 結構,這一結構與用於IP單元重組的 ipqueue 列表相連。當接收到更多的IP單元時,先找到正確的 ipq 結構,並為每個單元新建立一個 ipfrag 結構。每個 ipq 結構唯一地描述一個接收IP分割單元的,包括它的源和目的IP地址,上層協議標識和本IP幀的標識。當接收到所有的IP分割單元後,將它們重新組成一個 sk_buff ,然後交給上層協議處理。每個 ipq 中包含一個定時器,它在每接收到一個合法的單元後重新時。如果定時器到時, ipq 結構和它的一些 ipfrag 結構將被丟棄,傳送的信息則被假定為丟失。然後提交給層協議來重傳該信息。 

10.6  地址解析協議 (ARP)
    地址解析協議擔當了一個把IP地址翻譯成物理硬件地址如以太網地址的角色。IP在將數據(以 sk_buff 的形式)通過設備驅動傳送時需要這一轉換。

    它執行各種檢查,來看是否這一設備需要硬件頭,是否需要重建包的硬件頭。Linux緩存了硬件頭,這樣可以避頻繁重建。如果需要重建硬件頭,則調用設備指定的硬件頭重建例程。所有的以太網設備使用相同的頭重例程,這些例程將目的IP地址轉換成物理地址。

    ARP協議本身是很簡單的,它包括兩個消息類型,ARP請求與ARP應答。ARP請求包含了需要解析的IP地址,ARP應答(希望它)包含被解析的IP地址,硬件地址。ARP請求向連接在網絡上的所有主機廣播,因此,對於以網,所有連在網上的機器都能看到ARP請求。擁有ARP請求中的IP地址的機器將發出包含了它自己的物理地址ARP應答。

    ARP協議在Linux中是圍繞 arp_table 結構表來建立的,每個結構描述一個IP到物理地址的轉換。這些表項 在需要進行IP地址解析時生成,在隨時間變舊時被刪除。每個 arp_table 結構有如下字段: 

  last used  本ARP項最近一次使用的時間  
last updated  本ARP項最近一次更新的時間  
flags  描述本項的狀態,如是否完成等  
IP address  本項描述的IP地址  
hardware address  要解析的硬件地址  
hardware header  指向緩存硬件頭的指針  
timer  是個 timer_list 項,用於ARP請求沒有響應時的超時  
retries  ARP請求重試的次數  
sk_buff queue  等待IP地址解析的sk_buff項列表  


    ARP表包括了指向 arp_table 鏈的指針( arp_table 向量)。緩存這些表項可以加速對它們的訪問,每個表項用IP地址的最後兩個字節來生成索引,然後就可以查找表鏈以找到正確的表項。Linux也以 hh_cache 結構的形式來緩存 arp_table 項的預建的硬件頭。 

    請求一個IP地址解析並且沒有相應的 arp_table 項時,ARP必須發送一個ARP請求。它在表和sk_buff隊列中 生成一個新的 arp_table 項, sk_buff 包含了需要進行地址解析的網絡包。發送ARP請求時運行ARP定時器。如果沒有響應,ARP將重試幾次,如果仍然沒有響應,ARP將刪除該 arp_table 項。同時會通知隊列中等待IP地址解析的 sk_buff 結構,傳送它們的上層協議將處理這一失敗。UDP不關心丟包,而TCP則會建立TCP連接進行重傳。如果IP地址的所有者返回了它的硬件地址,則 arp_table 項被標記為完成,隊列中的sk_buff將被刪除,傳輸動作繼續。硬件地址被寫到每個sk_buff的硬件頭中。 

    ARP協議層必須響應ARP請求。它注冊它的協議類型(ETH_P_ARP),生成一個 packet_type 結構。這表示它將檢查網絡設備收到的所有ARP包。與ARP應答一樣,這包括ARP請求。用保存在接收設備的 device 結構中的硬件地址來生成ARP應答。 

    網絡拓撲結構會隨時間改變,IP地址會被重新分配不同的硬件地址。例如,一些撥號服務為每一次新建的連接分配一個IP地址。為了使ARP表包含這些數據項,ARP運行一個周期性的定時器,用來查看所有的 arp_table 項中哪一個超時。要注意不要移除包含一個或多個緩存硬件頭的項。移除這些項是很危險的,因為其它的數據結構要用到它們。一些 arp_table 項被標記為永久的,它們不會被釋放。ARP表不能太大;每個 arp_table 項會消耗一些核心內存。要分配一個新的表項而ARP表的大小已經到達它的最大值時,就要查找並刪除最老的表項。 

10.7  IP 路由
    IP路由函數決定了將預定的有指定IP地址的IP包送到哪。在傳送IP包時有很多種選擇。能最終到達目標嗎?如果能,要用到哪個網絡設備呢?如果有多於一個的網絡設備可被使用,哪一個是較好的呢?IP路由數據庫裡存的信息給出了這些問題的答案。有兩個數據庫,最重要的一個是Forwarding Information Database。這是一個有關已知的目的IP和它們的最佳路由的祥細列表,route cache則用來快速找到目的IP的路由。和其它的緩一樣,它包含的只是常用的路由;它的內容來自Forwarding Information Database。 

    通過IOCTL請求可將路由加入到BSD socket接口或從中刪除。這些是通過協議來實現的。INET協議層只允許 處理有超級用戶權限的IP路由的添加與刪除。這些路由可以是固定的,也可以隨時間而動態改變。大多數系統使用固定路由。路由器運行路由協議,路由協議持續地檢查所有已知目的IP的可得到的路由。沒有路由器的系統是端系統。路由協議是作為一個守護進程來實現的,如GATED,它們也用IOCTL來向BSD socket接口添加和刪除路由。 

10.7.1  路由緩存
    無論什麼時候查找IP路由,首先都要在路由緩存中檢查是否有匹配的路由。如果路由緩存裡沒有匹配的路由,則要從Forwarding Information Database中查找路由。如果那裡也沒有找到路由,則IP包發送失敗並通知應用程序。如果在路由緩存中沒有而在Forwarding Information Database中找到路由,則會為些路由生成一個新項,並添加到路由緩存中。路由緩存是一個表(ip_rt_hash_table),它包括指向rtable數據結構鏈的指針。hash函數利用IP地址中最小最重要的兩個字節來從路由表中進行索引。這兩個字節是在目的與提供的最佳hash值間是不同的。第個rtable項包含路由信息,目的IP地址,用於到達那個IP地址的網絡設備,信息大小的最大值等等。它還有一個reference count,一個usage count和一個最近一次被用的時間信息(在 jiffies 裡)。reference count在每次路由後增加,用於顯示該次路由的網絡連接數。它在應用程序停止使用路由時減小。useage count在每次查找路由時增加,用於將 rtable 項在它的hash鏈中排序。路由緩存中的對於所有項的最後被用時間信息將被周期性地檢查,以確定是否 rtable 已經舊了。如果某一路由最近沒有被使用,則從路由緩存中將之丟棄。由於路由緩存中的路由在有序的,所以常用的路由會排在hash鏈的前面。這意味著能更快地找到這些路由。 


10.7.2  The Forwarding Information Database





圖 10.5: The Forwarding Information Database 
    Forwarding Information Database(如圖  10.5 所示)包含對當前系統當前時間可得到的IP路由。它是一個 很復雜的數據結構,盡管進行了合理有效的安排,它仍然不是一個快速的數據庫。特別是要在這個數據庫中為每一要傳送的IP包查找目的地時將會非常慢。這就是要用路由緩存的原因:可以用已知的好的路由來加速IP包的傳送。路由緩存中的路由來源於Forwarding Information Database。 

    每個IP子網用一個 fib_zone 結構來描述。 fib_zone hash表指向著這些結構。hash索引來源於IP子網掩碼。所有通向同一子網的路由由 fib_node 和 fib_info 結構來描述,這兩結構在每個 fib_zone 結構的 fz_list 中排隊。如果這個子網中的路由數增大,則生成一個hash表,以使查找 fib_node 結構更加容易。 

    通向同一子網可以有多個路由,這些路由可能通過多個網關中的一個。IP路由層不允許用同一個網關對一個子網有多於一個的路由。換言之,如果通向同一子網有多個路由,則每個路由要保證使用一個不同的網關。與每個路由相關的有一個 metric 結構。它用來測量該路由有多優。一個路由的 metric 實質上是它在到達目的子網前所經過的IP子網數。 metric 越大,路由越差。 

  


--------------------------------------------------------------------------------

腳注:
1 National Science Foundation 

2 Synchronous Read Only Memory 

3 duh? What used for? 

Copyright © Linux教程網 All Rights Reserved