四、網絡卡的資料接收
核心如何從網絡卡接受資料,傳統的經典過程:
1、資料到達網絡卡;
2、網絡卡產生乙個中斷給核心;
3、核心使用i/o指令,從網絡卡i/o區域中去讀取資料;
我們在許多網絡卡驅動中,都可以在網絡卡的中斷函式中見到這一過程。
但是,這一種方法,有一種重要的問題,就是大流量的資料來到,網絡卡會產生大量的中斷,核心在中斷上下文中,會浪費大量的資源來處理中斷本身。所以,乙個問題是,「可不可以不使用中斷」,這就是輪詢技術,所謂napi技術,說來也不神秘,就是說,核心遮蔽中斷,然後隔一會兒就去問網絡卡,「你有沒有資料啊?」……
從這個描述本身可以看到,哪果資料量少,輪詢同樣占用大量的不必要的cpu資源,大家各有所長吧,呵呵……
ok,另乙個問題,就是從網絡卡的i/o區域,包括i/o暫存器或i/o記憶體中去讀取資料,這都要cpu去讀,也要占用cpu資源,「cpu從i/o區域讀,然後把它放到記憶體(這個記憶體指的是系統本身的物理記憶體,跟外設的記憶體不相干,也叫主記憶體)中」。於是自然地,就想到了dma技術——讓網絡卡直接從主記憶體之間讀寫它們的i/o資料,cpu,這兒不**事,自己找樂子去:
1、首先,核心在主記憶體中為收發資料建立乙個環形的緩衝佇列(通常叫dma環形緩衝區)。
2、核心將這個緩衝區通過dma對映,把這個佇列交給網絡卡;
3、網絡卡收到資料,就直接放進這個環形緩衝區了——也就是直接放進主記憶體了;然後,向系統產生乙個中斷;
4、核心收到這個中斷,就取消dma對映,這樣,核心就直接從主記憶體中讀取資料;
——呵呵,這乙個過程比傳統的過程少了不少工作,因為裝置直接把資料放進了主記憶體,不需要cpu的干預,效率是不是提高不少?
對應以上4步,來看它的具體實現:
1、分配環形dma緩衝區
linux核心中,用skb來描述乙個快取,所謂分配,就是建立一定數量的skb,然後把它們組織成乙個雙向鍊錶;
2、建立dma對映
核心通過呼叫
dma_map_single(struct device *dev,void *buffer,size_t size,enumdma_#_direction direction)
建立對映關係。
struct device *dev,描述乙個裝置;
size:快取大小;
direction:對映方向——誰傳給誰:一般來說,是「雙向」對映,資料在裝置和記憶體之間雙向流動;
對於pci裝置而言(網絡卡一般是pci的),通過另乙個包裹函式pci_map_single,這樣,就把buffer交給裝置了!裝置可以直接從裡邊讀/取資料。
3、這一步由硬體完成;
4、取消對映
dma_unmap_single,對pci而言,大多呼叫它的包裹函式pci_unmap_single,不取消的話,快取控制權還在裝置手裡,要呼叫它,把主動權掌握在cpu手裡——因為我們已經接收到資料了,應該由cpu把資料交給上層網路棧;
當然,不取消之前,通常要讀一些狀態位資訊,諸如此類,一般是呼叫
dma_sync_single_for_cpu()
讓cpu在取消對映前,就可以訪問dma緩衝區中的內容。
ok,有了這些知識,我們就可以來看e100的**了,它跟上面講的步驟基本上一樣的——繞了這麼多圈子,就是想繞到e100上面了,呵呵!
在e100_open函式中,呼叫e100_up,我們前面分析它時,略過了乙個重要的東東,就是環形緩衝區的建立,這一步,是通過
e100_rx_alloc_list函式呼叫完成的:
[cpp]view plain
copy
static
int e100_rx_alloc_list(struct nic *nic)
} nic->rx_to_use = nic->rx_to_clean = nic->rxs;
nic->ru_running = ru_suspended;
return 0;
}
[cpp]view plain
copy
#define rfd_buf_len (sizeof(struct rfd) + vlan_eth_frame_len)
static
inline
int e100_rx_alloc_skb(struct nic *nic, struct rx *rx)
if(rx->prev->skb)
return 0;
}
e100_rx_alloc_list函式在乙個迴圈中,建立了環形緩衝區,並呼叫e100_rx_alloc_skb為每個緩衝區分配了空間,並做了
dma對映。這樣,我們就可以來看接收資料的過程了。
前面我們講過,中斷函式中,呼叫netif_rx_schedule,表明使用輪詢技術,系統會在未來某一時刻,呼叫裝置的poll函式
[cpp]view plain
copy
static
int e100_poll(struct net_device *netdev, int *budget)
*budget -= work_done;
netdev->quota -= work_done;
return 1;
}
目前,我們只關心rx,所以,e100_rx_clean函式就成了我們關注的對像,它用來從緩衝佇列中接收全部資料(這或許是取名為clean的原因吧!
[cpp]view plain
copy
static
inline
void e100_rx_clean(struct nic *nic, unsigned int *work_done,
unsigned int work_to_do)
else
if(-eno# == err)
break;
} if(restart_required)
rx_to_start = nic->rx_to_clean;
for(rx = nic->rx_to_use; !rx->skb; rx = nic->rx_to_use = rx->next)
if(restart_required)
}
[cpp]view plain
copy
static
inline
int e100_rx_indicate(struct nic *nic, struct rx *rx,
unsigned int *work_done, unsigned int work_to_do)
else
if(actual_size > nic->netdev->mtu + vlan_eth_hlen) else
rx->skb = null;
return 0;
}
網絡卡驅動執行到這裡,資料接收的工作,也就處理完成了。但是,使用這一種方法的驅動,省去了網路棧中乙個重要的內容,就是
「佇列層」,讓我們來看看,傳統中斷接收資料報模式下,使用netif_rx函式呼叫,又會發生什麼
Linux核心資料報處理流程 資料報接收 3
五 佇列層 1 軟中斷與下半部 當用中斷處理的時候,為了減少中斷處理的工作量,比如,一般中斷處理時,需要遮蔽其它中斷,如果中斷處理時間過長,那麼其它中斷 有可能得不到及時處理,也以,有一種機制,就是把 不必馬上處理 的工作,推遲一點,讓它在中斷處理後的某乙個時刻得到處理。這就 是下半部。下半部只是乙...
Linux核心資料報格式
linux kernel中mac頭,ip頭,tcp頭結構體定義,核心列印方式。mac頭 核心中mac頭結構體 defein eth alen 6 struct ethhdr attribute packed define mac fmt 02x 02x 02x 02x 02x 02x define ...
Linux核心資料結構
一 概述 linux核心提供了一些常用的資料結構並建議開發者盡量重用,而不需要再去實現一些類似的資料結構。這篇部落格主要描述一下linux核心提供的鍊錶 佇列 對映及二叉樹這四種常用資料結構。當然這裡提到的每一種資料結構要說清楚都需要不少的篇幅,而這篇博文只是簡要分析一下linux核心設計的理念,以...