Linux強大的網路功能是如何?的,讓我們一起進入Linux核心的網路系統瞭解一下吧。
7.1. sk_buff結構
在Linux核心的網路實現中,使用了一個緩衝結構(struct sk_buff)來管理網路報文,這個緩衝區也叫通訊端緩衝。sk_buff是核心網路子系統中最重要的一種資料結構,它貫穿網路報文收發的整個周期。該結構在核心源碼的include/linux/skbuff.h檔案中定義。我們有必要瞭解結構中每個欄位的意義。
一個通訊端緩衝由兩部份組成:
· 報文資料:儲存實際需要通過網路發送和接收的資料。
· 管理資料(struct sk_buff):管理報文所需的資料,在sk_buff結構中有一個head指標指向記憶體中報文資料開始的位置,有一個data指標指向報文資料在記憶體中的具體地址。head和data之間申請有足夠多的空間用來存放報文頭資訊。
struct sk_buff結構在記憶體中的結構:
sk_buff
----------------------------------- ------------> skb->head
| headroom |
|-----------------------------------| ------------> skb->data
| DATA |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|-----------------------------------| ------------> skb->tail
| tailroom |
----------------------------------- ------------> skb->end
7.2. sk_buff結構操作函數
核心通過alloc_skb()和dev_alloc_skb()為通訊端緩衝申請記憶體空間。這兩個函數的定義位於net/core/skbuff.c檔案內。通過這alloc_skb()申請的記憶體空間有兩個,一個是存放實際報文資料的記憶體空間,通過kmalloc()函數申請;一個是sk_buff資料結構的記憶體空間,通過 kmem_cache_alloc()函數申請。dev_alloc_skb()的功能與alloc_skb()類似,它只被驅動程式的中斷所調用,與alloc_skb()比較只是申請的記憶體空間長度多了16個位元組。
核心通過kfree_skb()和dev_kfree_skb()釋放為通訊端緩衝申請的記憶體空間。dev_kfree_skb()被驅動程式使用,功能與kfree_skb()一樣。當skb->users為1時kfree_skb()才會執行釋放記憶體空間的動作,否則只會減少skb->users的值。skb->users為1表示已沒有其他使用者使用該緩衝了。
skb_reserve()函數為skb_buff緩衝結構預留足夠的空間來存放各層網路通訊協定的頭資訊。該函數在在skb緩衝申請成功後,載入報文資料前執行。在執行skb_reserve()函數前,skb->head,skb->data和skb->tail指標的位置的一樣的,都位於skb記憶體空間的開始位置。這部份空間叫做headroom。有效資料後的空間叫tailroom。skb_reserve的操作只是把skb->data和skb->tail指標向後移,但緩衝總長不變。
運行skb_reserve()前sk_buff的結構
sk_buff
---------------------- ----------> skb->head,skb->data,skb->tail
| |
| |
| |
| |
| |
| |
| |
| |
| |
--------------------- ----------> skb->end
運行skb_reserve()後sk_buff的結構
sk_buff
---------------------- ----------> skb->head
| |
| headroom |
| |
|--------------------- | ----------> skb->data,skb->tail
| |
| |
| |
| |
| |
--------------------- ----------> skb->end
skb_put()向後擴大資料區空間,tailroom空間減少,skb->data指標不變,skb->tail指標下移。
skb_push()向前擴大資料區空間,headroom空間減少,skb->tail指標不變,skb->data指標上移
skb_pull()縮小資料區空間,headroom空間增大,skb->data指標下移,skb->tail指標不變。
skb_shared_info結構位於skb->end後,用skb_shinfo函數申請記憶體空間。該結構主要用以描述data記憶體空間的資訊。
--------------------- -----------> skb->head
| |
| |
| sk_buff |
| |
| |
| |
|---------------------| -----------> skb->end
| |
| skb_share_info |
| |
---------------------
skb_clone和skb_copy可拷貝一個sk_buff結構,skb_clone方式是clone,只產生新的sk_buff記憶體區,不會產生新的data記憶體區,新sk_buff的skb->data指向舊data記憶體區。skb_copy方式是完全拷貝,產生新的sk_buff記憶體區和data記憶體區。。
7.3. net_device結構
net_device結構是Linux核心中所有網路裝置的基礎資料結構。包含網路介面卡的硬體資訊(中斷、連接埠、驅動程式函數等)和高層網路通訊協定的網路設定資訊(IP地址、子網路遮罩等)。該結構的定義位於include/linux/netdevice.h
每個net_device結構表示一個網路裝置,如eth0、eth1...。這些網路裝置通過dev_base線性錶鏈接起來。核心變數dev_base表示登入網路裝置列表的進入點,它指向列表的第一個元素(eth0)。然後各元素用next欄位指向下一個元素(eth1)。使用ifconfig -a命令可以查看系統中所有登入的網路裝置。
net_device結構通過alloc_netdev函數分配,alloc_netdev函數位於net/core/dev.c檔案中。該函數需要三個參數。
· 私人資料結構的大小
· 裝置名稱,如eth0,eth1等。
· 配置常式,這些常式會初始化部分net_device欄位。
分配成功則返回指向net_device結構的指標,分配失敗則返回NULL。
7.4. 網路裝置初始化
在使用網路裝置之前,必須對它進行初始化和向核心註冊該裝置。網路裝置的初始化包括以下步驟:
· 硬體初始化:分配IRQ和I/O連接埠等。
· 軟體初始化:分配IP地址等。
· 功能初始化:QoS等
7.5. 網路裝置與核心的溝通方式
網路裝置(網卡)通過輪詢和中斷兩種方式與核心溝通。
· 輪詢(polling),由核心發起,核心周期性地檢查網路裝置是否有資料要處理。
· 中斷(interrupt),由裝置發起,裝置向核心發送一個硬體中斷訊號。
Linux網路系統可以結合輪詢和中斷兩方式以提高網路系統的效能。共小節重點介紹中斷方式。
每個中斷都會調用一個叫中斷處理器的函數。當驅動程式向核心註冊一個網卡時,會請求和分配一個IRQ號。接著為分配的這個IRQ註冊中斷處理器。註冊和釋放中斷處理器的代碼是架構相關的,不同的硬體平台有不同的代碼實現。實現代碼位於kernel/irq/manage.c和arch/XXX/kernel/irq.c源碼檔案中。XXX是不同硬體架構的名稱,如我們所使用得最多的i386架構。下面是註冊和釋放中斷處理器的函數原型。
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
void free_irq(unsigned int irq, void *dev_id)
核心是通過IRQ號來找到對應的中斷處理器並執行它的。為了找到中斷處理器,核心把IRQ號和中斷處理器函數的關聯起來儲存在全域表(global table)中。IRQ號和中斷處理器的關聯性可以是一對一,也可以是一對多。因為IRQ號是可以被多個裝置所共用的。
通過中斷,網卡裝置可以向驅動程式傳送以下資訊:
· 幀的接收,這是最用的中斷類型。
· 傳送失敗通知,如傳送逾時。
· DMA傳送成功。
· 裝置有足夠的記憶體傳送資料幀。當外出隊列沒有足夠的記憶體空間存放一個最大的幀時(對於乙太網路卡是1535),網卡產生一個中斷要求以後再傳送資料,驅動程式會禁止資料的傳送,。當有效記憶體空間多於裝置需傳送的最大幀(MTU)時,網卡會發送一個中斷通知驅動程式重新啟用資料傳送。這些邏輯處理在網路卡驅動程式中設計。 netif_stop_queue()函數禁止裝置傳送隊列,netif_start_queue()函數重啟裝置的傳送隊列。這些動作一般在驅動程式的xxx_start_xmit()中處理。
系統的中斷資源是有限的,不可能為每種裝置提供獨立的中斷號,多種裝置要共用有限的中斷號。上面我們提到中斷號是和中斷處理器關聯的。在中斷號共用的情況下核心如何正確找到對應的中斷處理器呢?核心採用一種最簡單的方法,就是不管三七二一,當同一中斷號的中斷髮生時,與該中斷號關聯的所有中斷處理器都一起被調用。調用後再靠中斷處理器中的過濾程式來篩選執行真正的中斷處理。
對於使用共用中斷號的裝置,它的驅動程式在註冊時必須先指明允許中斷共用。
IRQ與中斷處理器的映射關係儲存在一個向量表中。該表儲存了每個IRQ的中斷處理器。向量表的大小是平台相關的,從15(i386)到超過200都有。 irqaction資料結構儲存了映射表的資訊。上面提到的request_irq()函數建立irqaction資料結構並通過setup_irq()把它加入到irq_des向量表中。irq_des在 kernel/irq/handler.c中定義,平台相關的定義在arch/XXX/kernel/irq.c檔案中。setup_irq()在kernel/irq/manage.c,平台相關的定義在arch/XXX/kernel/irq.c中。
7.6. 網路裝置操作層的初始化
在系統啟動階段,網路裝置操作層通過net_dev_init()進行初始化。net_dev_init()的代碼在net/core/dev.c檔案中。這是一個以__init標識的函數,表示它是一個低層的代碼。
net_dev_init()的主要初始化工作內容包括以下幾點:
· 產生/proc/net目錄和目錄下相關的檔案。
7.7. 核心模組載入器
kmod是核心模組載入器。該載入器在系統啟動時會觸發/sbin/modprobe和/sbin/hotplug自動載入相應的核心模組和運行裝置啟動指令碼。modprobe使用/etc/modprobe.conf設定檔。當該檔案中有"alias eth0 3c59x"配置時就會自動加3c59x.ko模組。
7.8. 虛擬設備
虛擬設備是在真實裝置上的虛擬,虛擬設備和真實裝置的對應關係可以一對多或多對一。即一個虛擬設備對應多個真實裝置或多個真實裝置一個虛擬設備。下面介紹網路子系統中虛擬設備的應用情況。
· Bonding,把多個真實網卡虛擬成一個虛擬網卡。對於應用來講就相當於訪問一個網路介面。
· 802.1Q,802.3乙太網路幀頭擴充,添加了VLAN頭資訊。把多個真實網卡虛擬成一個虛擬網卡。
· Bridging,一個虛擬橋接器,把多個真實網卡虛擬成一個虛擬網卡。
· Tunnel interfaces,實現GRE和IP-over-IP虛擬通道。把一個真實網卡虛擬成多個虛擬網卡。
· True equalizer (TEQL),類似於Bonding。
上面不是一個完整列表,隨著核心的不斷開發完善,新功能新應用也會不斷出現。
7.9. 8139too.c源碼分析
程式調用流程:
module_init(rtl8139_init_module)
static int __init rtl8139_init_module (void)
pci_register_driver(&rtl8139_pci_driver) #註冊驅動程式
static int __devinit rtl8139_init_one (struct pci_dev *pdev,
const struct pci_device_id *ent)
static int __devinit rtl8139_init_board (struct pci_dev *pdev,
struct net_device **dev_out)
dev = alloc_etherdev (sizeof (*tp)) #為裝置分配net_device資料結構
pci_enable_device (pdev) #啟用PCI裝置
pci_resource_start (pdev, 0) #擷取PCI I/O地區1的首地址
pci_resource_end (pdev, 0) #擷取PCI I/O地區1的尾地址
pci_resource_flags (pdev, 0) #擷取PCI I/O地區1資源標記
pci_resource_len (pdev, 0) #擷取地區資源長度
pci_resource_start (pdev, 1) #擷取PCI I/O地區2的首地址
pci_resource_end (pdev, 1) #擷取PCI I/O地區2的尾地址
pci_resource_flags (pdev, 1) #擷取PCI I/O地區2資源標記
pci_resource_len (pdev, 1) #擷取地區資源長度
pci_request_regions(pdev, DRV_NAME) #檢查其它PCI裝置是否使用了相同的地址資源
pci_set_master(pdev) #通過設定PCI裝置的命令寄存器允許DMA
7.10. 核心網路資料流
網路報文從應用程式產生,通過網卡發送,在另一端的網卡接收資料並傳遞給應用程式。這個過程網路報文在核心中調用了一系列的函數。下面把這些函數列舉出來,方便我們瞭解網路報文的流程。
發送流程:
write
|
sys_write
|
sock_sendmsg
|
inet_sendmsg
|
tcp_sendmsg
|
tcp_push_one
|
tcp_transmit_skb
|
ip_queue_xmit
|
ip_route_output
|
ip_queue_xmit
|
ip_queue_xmit2
|
ip_output
|
ip_finish_output
|
neith_connected_output
|
dev_queue_xmit ----------------|
| |
| queue_run
| queue_restart
| |
hard_start_xmit-----------------
接收流程:
netif_rx
|
netif_rx_schedule
|
_cpu_raise_softirq
|
net_rx_action
|
ip_rcv
|
ip_rcv_finish
|
ip_route_input
|
ip_local_deliver
|
ip_local_deliver_finish
|
tcp_v4_rcv
|
tcp_v4_do_rcv
|
tcp_rcv_established------------------|
| |
tcp_data_queue |
| |
_skb_queue_tail----------------------|
|
data_ready
|
sock_def_readable
|
wake_up_interruptible
|
tcp_data_wait
|
tcp_recvmsg
|
inet_recvmsg
|
sock_recvmsg
|
sock_read
|
read
資料包在應用程式層稱為data,在TCP層稱為segment,在IP層稱為packet,在資料連結層稱為frame。