內核如何從網(wǎng)卡接收數據,傳統的過(guò)程:
1.數據到達網(wǎng)卡;
2.網(wǎng)卡產(chǎn)生一個(gè)中斷給內核;
3.內核使用I/O指令,從網(wǎng)卡I/O區域中去讀取數據;
我們在許多網(wǎng)卡驅動(dòng)中(很老那些),都可以在網(wǎng)卡的中斷函數中見(jiàn)到這一過(guò)程。
但是,這一種方法,有一種重要的問(wèn)題,就是大流量的數據來(lái)到,網(wǎng)卡會(huì )產(chǎn)生大量的中斷,內核在中斷上下文中,會(huì )浪費大量的資源來(lái)處理中斷本身。所以,就有一個(gè)問(wèn)題,“可不可以不使用中斷”,這就是輪詢(xún)技術(shù),所謂NAPI技術(shù),說(shuō)來(lái)也不神秘,就是說(shuō),內核屏蔽中斷,然后隔一會(huì )兒就去問(wèn)網(wǎng)卡,“你有沒(méi)有數據???”……
從這個(gè)描述本身可以看到,如果數據量少,輪詢(xún)同樣占用大量的不必要的CPU資源,大家各有所長(cháng)吧
OK,另一個(gè)問(wèn)題,就是從網(wǎng)卡的I/O區域,包括I/O寄存器或I/O內存中去讀取數據,這都要CPU去讀,也要占用CPU資源,“CPU從I/O區域讀,然后把它放到內存(這個(gè)內存指的是系統本身的物理內存,跟外設的內存不相干,也叫主內存)中”。于是自然地,就想到了DMA技術(shù)——讓網(wǎng)卡直接從主內存之間讀寫(xiě)它們的I/O數據,CPU,這兒不干你事,自己找樂(lè )子去:
1.首先,內核在主內存中為收發(fā)數據建立一個(gè)環(huán)形的緩沖隊列(通常叫DMA環(huán)形緩沖區)。
2.內核將這個(gè)緩沖區通過(guò)DMA映射,把這個(gè)隊列交給網(wǎng)卡;
3.網(wǎng)卡收到數據,就直接放進(jìn)這個(gè)環(huán)形緩沖區了——也就是直接放進(jìn)主內存了;然后,向系統產(chǎn)生一個(gè)中斷;
4.內核收到這個(gè)中斷,就取消DMA映射,這樣,內核就直接從主內存中讀取數據;
——呵呵,這一個(gè)過(guò)程比傳統的過(guò)程少了不少工作,因為設備直接把數據放進(jìn)了主內存,不需要CPU的干預,效率是不是提高不少?
對應以上4步,來(lái)看它的具體實(shí)現:
1)分配環(huán)形DMA緩沖區
Linux內核中,用skb來(lái)描述一個(gè)緩存,所謂分配,就是建立一定數量的skb,然后用e1000_rx_ring 環(huán)形緩沖區隊列描述符連接起來(lái)
2)建立DMA映射
內核通過(guò)調用
dma_map_single(struct device *dev,void *buffer,size_t size,enum dma_data_direction direction)
建立映射關(guān)系。
struct device *dev 描述一個(gè)設備;
buffer:把哪個(gè)地址映射給設備;也就是某一個(gè)skb——要映射全部,當然是做一個(gè)雙向鏈表的循環(huán)即可;
size:緩存大??;
direction:映射方向——誰(shuí)傳給誰(shuí):一般來(lái)說(shuō),是“雙向”映射,數據在設備和內存之間雙向流動(dòng);
對于PCI設備而言(網(wǎng)卡一般是PCI的),通過(guò)另一個(gè)包裹函數pci_map_single,這樣,就把buffer交給設備了!設備可以直接從里邊讀/取數據。
3)這一步由硬件完成;
4)取消映射
dma_unmap_single,對PCI而言,大多調用它的包裹函數pci_unmap_single,不取消的話(huà),緩存控制權還在設備手里,要調用它,把主動(dòng)權掌握在CPU手里——因為我們已經(jīng)接收到數據了,應該由CPU把數據交給上層網(wǎng)絡(luò )棧;當然,不取消之前,通常要讀一些狀態(tài)位信息,諸如此類(lèi),一般是調用dma_sync_single_for_cpu()讓CPU在取消映射前,就可以訪(fǎng)問(wèn)DMA緩沖區中的內容。
原代碼分析
基于linux v2.6.26
//e1000_probe 網(wǎng)卡初始化 (重點(diǎn)關(guān)注兩部分 1注冊poll函數 2設置接收緩沖的大小)
static int __devinit e1000_probe(struct pci_dev *pdev,const struct pci_device_id *ent){
struct net_device *netdev;
struct e1000_adapter *adapter;
....
err=pci_enable_device(pdev);
...
err=pci_set_dma_mask(pdev,DMA_64BIT_MASK); //設置pci設備的dma掩碼
...
netdev = alloc_etherdev(sizeof(struct e1000_adapter)); //為e1000網(wǎng)卡對應的net_device結構分配內存
...
pci_set_drvdata(pdev,netdev);
adapter=netdev_priv(netdev);
adapter->netdev=netdev;
adapter->pdev=pdev;
...
mmio_start = pci_resource_start(pdev,0);
mmio_len = pci_resource_len(pdev,0);
....
/*將e1000網(wǎng)卡驅動(dòng)的相應函數注冊到net_device中*/
netdev->open = &e1000_open;
netdev->stop = &e1000_close;
...
netif_napi_add(netdev,&adapter->napi,e1000_clean,64); // 注冊poll函數為e1000_clean, weight為64
...
netdev->mem_start = mmio_start;
netdev->mem_end = mmio_start+mmio_len;
....
if(e1000e_read_mac_addr(&adapter->hw)) ndev_err(...); //從網(wǎng)卡設備的EEPROM中讀取mac地址
memcpy(netdev->dev_addr, adapter->hw.mac.addr, netdev->addr_len);
memcpy(netdev->perm_addr, adapter->hw.mac.addr, netdev->addr_len);
....
adapter->rx_ring->count = 256; //設置接收環(huán)型緩沖區隊列的缺省大小
...
e1000_reset(adapter);
...
strcpy(netdev->name,"eth%d");
err= register_netdev(netdev); //將當前網(wǎng)絡(luò )設備注冊到系統的dev_base[]設備數組當中
....
return 0;
}
e1000_open 各種數據結構初始化 (環(huán)形緩沖區隊列的初始化)
static int e1000_open(struct net_device *netdev){
struct e1000_adapter *adapter = netdev_priv(netdev);
....
err = e1000_setup_all_rx_resoures(adapter) //預先分配緩沖區資源
....
err = e1000_request_irq(adapter); //分配irq中斷
....
}
int e1000_setup_all_rx_resources(struct e1000_adapter *adapter){
int i,err=0;
for(i=0 ; i<adapter->num_rx_queues ; i++){
err = e1000_setup_rx_resources(adapter,&adapter->rx_ring[i]);
if(err){
...
}
}
return err;
}
e1000_rx_ring 環(huán)形緩沖區隊列(接收緩沖隊列由多個(gè)描述符組成,每個(gè)描述符中都包含一個(gè)緩沖區buffer,該buffer以dma方式存放數據包,整個(gè)緩沖隊列以環(huán)形排列 每個(gè)描述符都有一個(gè)狀態(tài)變量以表示該緩沖區buffer是否可以被新到的數據包覆蓋)
struct e1000_rx_ring{
void *desc; //指向該環(huán)形緩沖區
dma_addr_t dma; //dma物理地址
unsigned int size;
unsigned int count; //環(huán)形隊列由多少個(gè)描述符組成,這個(gè)在probe中定義了
unsigned int next_to_use; //下一個(gè)可使用的描述符號
unsigned int next_to_clean; //該描述符狀態(tài)(是否正在使用,是否臟)
struct e1000_buffer *buffer_info; //緩沖區buffer
...
}
struct e1000_buffer{
struct sk_buff *skb;
....
}
static int e1000_setup_rx_resources(struct e1000_adapt *adapter, struct e1000_rx_ring *rxdr){
struct pci_dev *pdev = adapter->pdev;
int size,desc_len;
size = sizeof(struct e1000_buffer) * rxdr->count;
rxdr->buffer_info = vmalloc(size);
memset(rxdr->buffer_info,0,size); //分配buffer所使用的內存
....
if(adapter->hw.mac_type <= e1000_82547_rec_2)
desc_len = sizeof(struct e1000_rx_desc);
else ....
rxdr->size = rxdr->count * desc_len;
rxdr->size = ALIGN(rxdr->size,4096);
rxdr->desc = pci_alloc_consistent(pdev,rxdr->size,&rxdr->dma);
...
memset(rxdr->desc,0,rxdr->size); //分配緩沖隊列所使用的內存
rxdr->next_to_clean =0;
rxdr->next_to_use =0;
return 0;
}
e1000_up 啟動(dòng)網(wǎng)卡函數 調用alloc_rx_buf來(lái)建立環(huán)形緩沖隊列
int e1000_up(struct e1000_adapter *adapter){
e1000_configure(adatper);
....
}
static void e1000_configure(struct e1000_adapter *adapter){
struct net_device *netdev = adapter->netdev;
int i;
...
e1000_configure_rx(adapter);
...
for (i=0;i<adapter->num_rx_queues;i++){
struct e1000_rx_ring *ring = &adapter ->rx_ring[i];
adapter->alloc_rx_buf(adapter,ring,E1000_DESC_UNUSED(ring)); //從這里就可以看出 環(huán)形緩沖區并不是一開(kāi)始就完全建好的,建了部分
}
...
}
static void e1000_configure_rx(struct e1000_adapter *adapter){
....
adapter->clean_rx = e1000_clean_rx_irq; //后面會(huì )提到的poll()
adapter->alloc_rx_buf = e1000_alloc_rx_irq //建立環(huán)形緩沖隊列函數 這里實(shí)際調用的是e1000_alloc_rx_buffers
}
e1000_alloc_rx_buffers ----因為其中有些參數要看完下面的才能理解,所以這個(gè)函數最后再寫(xiě)
e1000_intr e1000的中斷處理函數
static irqreturn_t e1000_intr(int irq,void *data){
struct net_device *netdev = data;
struct e1000_adapter *adapter = netdev_priv(netdev);
..
u32 icr = E1000_READ_REG(hw,ICR);
#ifdef CONFIG_E1000_NAPI
int i;
#endif
...
#ifdef CONFIG_E1000_NAPI //進(jìn)入輪詢(xún)模式
if(unlikely(hw->mac_type<e1000_82571)){
E1000_WRITE_REG(hw,IMC,~0); //關(guān)閉中斷
E1000_WRITE_FLUSH(hw);
}
if (likely(netif_rx_schedule_prep(netdev,&adapter->napi))){ //確定該設備處于運行狀態(tài), 而且還未被添加到網(wǎng)絡(luò )層的poll隊列中
...
__netif_rx_schedule(netdev,&adapter->napi); //將當前設備netdevice加到與cpu相關(guān)的softnet_data的輪旬設備列表poll_list中并觸發(fā)NET_RX_SOFTIRQ軟中斷
}
#else //進(jìn)入中斷模式
{ ...
for(i=0;i<E1000_MAX_INTR;i++){
if (unlikely(!adapter->clean_rx(adapter, adapter->rx_ring) &.... //執行clean_rx()中關(guān)于中斷模式的代碼 不走napi路徑
break;
...
}
}
....
return IRQ_HANDLED;
}
static inline int netif_rx_schedule_prep(struct net_device *dev,struct napi_struct *napi){
return napi_schedule_prep(napi);
}
static inline int napi_schedule_prep(struct napi_struct *n){
return !napi_disable_pending(n) &&
!test_and_set_bit(NAPI_STATE_SCHED, &n->state); //測試該設備是否已被被加到poll隊列
}
static inline int napi_disable_pending (struct napi_struct *n){
return test_bit(NAPI_STATE_DISABLE,&n->state); //測試該設備是否停止運行
}
static inline void __netif_rx_schedule(struct net_device *dev,struct napi_struct *napi){
__napi_schedule(napi);
}
void __napi_schedule(struct napi_struct *n){
unsigned long flags;
local_irq_save(flags);
list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);
__raise_softirq_irqoff(NET_RX_SOFTIRQ); 觸發(fā)軟中斷
local_irq_restore(flags);
}
#define __raise_softirq_irqoff(nr) do {or_softirq_pending(iUL<<(nr)); } while(0)
用到的數據結構napi_struct
struct napi_struct{
struct list_head poll_list; //poll_list鏈表
unsigned long state //設備狀態(tài)信息 往上看看
int weight; //設備預處理數據配額,作用是輪詢(xún)時(shí)更公平的對待各個(gè)設備
int (*poll) (strcut napi_struct *,int);
.....
}
接下來(lái)就是軟中斷處理函數net_rx_action()
static void net_rx_action(struct softirq_action *h){
struct list_head *list = &__get_cpu_var(softnet_data).poll_list;
unsigned long start_time = jiffies;
int budget = netdev_budget; //處理限額,一次最多只能處理這么多數據包
....
local_irq_disable();
while (!list_empty(list)){
struct napi_struct *n;
int work,
weight;
if (unlikely(budget < 0 || jiffies != start_time)) //如果實(shí)際工作量work 超過(guò)限額,或處理時(shí)間超過(guò)1秒,則出于系統響應考慮立即從軟中斷處理函數中跳出來(lái), work是poll的返回值 限額budget每次都會(huì )根據返回的work值重新計算 ,配額weight和work配合來(lái)實(shí)現輪詢(xún)算法,具體算法要看完e1000_clean(),e1000_rx_irq()才能清楚
goto softnet_break;
local_irq_enalbe();
n = list_entry(list->next,struct napi_struct,poll_list);
weight = n->weight;
work 0;
if (test_bit(NAPI_STATE_SCHED,&n->state))
work = n->poll(n,weight); //調用設備的poll函數e1000_clean()
....
budget -= work; //更新限額
local_irq_disable();
if (unlikely(work == weight)){ //處理量大于配額
if(unlikely(napi_disable_pending(n)))
__napi_complete(n);
else
list_move_tail(&n->poll_list,list); //該設備還有要接收的數據沒(méi)被處理,因為輪詢(xún)算法 被移動(dòng)到poll_llst尾部等待處理
}
...
}
out:
local_irq_enable();
...
return;
softnet_break:
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
goto out;
}
e1000網(wǎng)卡poll函數 e1000_clean()
static int e1000_clean(struct napi_struct *napi,int budget){ //此處的budget實(shí)際上是傳過(guò)來(lái)的weight,不要和上面的budget弄混了
struct e1000_adapter *adapter = container_of(napi,struct e1000_adapter,napi);
struct net_device *poll_dev = adapter->netdev;
int work_done = 0;
adapter = poll_dev->priv;
.....
adapter ->clean_rx(adapter,&adapter ->rx_ring[0],&work_done,budget); //實(shí)際調用的是clean_rx_irq()
...
if(work_done<budget){ //如果完成的工作量(已處理了的接收數據)小于weight(budget=weight), 則說(shuō)明處理完成了該設備所有要接收的數據包, 之后調用netif_rx_complete()將該設備從poll_list鏈表中刪除,并打開(kāi)中斷退出輪詢(xún)模式
...
netif_rx_complete(poll_dev,napi); //__napi_complete()的包裝函數
e1000_irq_enable(adapter); //開(kāi)中斷
}
return work_done;
}
static inline void netif_rx_complete(struct net_device *dev,struct napi_struct *napi){
unsigned long flags;
local_irq_save(flags);
__netif_rx_complete(dev,napi);
local_irq_restore(flags);
}
static inline void __netif_rx_complete(struct net_device *dev,struct napi_struct *napi){
__napi_complete(napi);
}
static inline void __napi_complete(struct napi_struct *n){
....
list_del(&n->poll_list);
clear_bit(NAPI_STATE_SCHED,&n->state);
}
設備輪詢(xún)接收機制中最重要的函數e1000_clean_rx_irq()
#ifdef CONFIG_E1000_NAPI
e1000_clean_rx_irq(struct e1000_adapter *adapter,struct e1000_rx_ring *rx_ring,int *work_done,int work_to_do) //work_to_do實(shí)際上是傳過(guò)來(lái)的配額weight
.....
{
struct net_device *netdev = adapter->netdev;
struct pci_dev *pdev = adapter->pdev;
struct e1000_rx_desc *rx_desc,*next_rxd;
struct e1000_buffer *buffer_info, *next_buffer;
...
unsigned int i;
int cleaned_count = 0;
....
i = rx_ring->next_to_clean; //next_to_clean是下一個(gè)可以被清除的描述符索引,上面講過(guò)環(huán)形緩沖隊列由多個(gè)描述符組成,每個(gè)描述符都有一個(gè)用于存放接收數據包的緩沖區buffer,這里所說(shuō)的“可以被清除”并不是將其刪除,而是標記這個(gè)緩沖區的數據已經(jīng)處理(可能正在處理),但是否處理完了要看rx_desc->status&E1000_RXD_STAT_DD,當有新數據需要使用緩沖區時(shí),只是將已處理的緩沖區覆蓋而已, 這里的i可以理解為可以被新數據覆蓋的緩沖區序號
rx_desc = E1000_RX_DESC(*rx_ring,i); //得到相應的描述符
buffer_info = &rx_ring->buffer_info[i];
while(rx_desc->status & E1000_RXD_STAT_DD){ //測試其狀態(tài)是否為已刪除
struct sk_buff *skb;
u8 status;
#ifdef CONFIG_E1000_NAPI
if (*wrok_done>=work_to_do) //如果所完成的工作>配額則直接退出
break;
(*work_done) ++
#endif
status = rx_desc->status;
skb = buffer_info->skb; //得到緩沖區中的數據
buffer_info->skb = NULL;
prefetch(skb->data-NET_IP_ALIGN);
if(++i == rx_ring->count) //處理環(huán)形緩沖區達到隊列末尾的情況,因為是環(huán)形的,所以到達末尾的下一個(gè)就是隊列頭,這樣整個(gè)隊列就不斷地循環(huán)處理。然后獲取下一格描述符的狀態(tài),看看是不是處理刪除狀態(tài)。如果處于就會(huì )將新到達的數據覆蓋舊的緩沖區,如果不處于則跳出循環(huán),并將當前緩沖區索引號置為下一次查詢(xún)的目標
i = 0;
next_rxd = E1000_RX_DESC(*rx_ring,i);
next_buffer = &rx_ring->buffer_info[i];
cleaned = true ;
cleaned_count ++;
pci_unmap_single(pdev,buffer_info->dma,buffer_info->length,PCI_DMA_FROMDEVICE); //* 取消映射,因為通過(guò)DMA,網(wǎng)卡已經(jīng)把數據放在了主內存中,這里一取消,也就意味著(zhù),CPU可以處理主內存中的數據了 */
....
//checksum
...
#ifdef CONFIG_E1000_NAPI
netif_receive_skb(skb); //交由上層協(xié)議處理 , 如果數據包比較大,處理時(shí)間會(huì )相對較長(cháng)
#else
netif_rx(skb); //進(jìn)入中斷模式 將數據包插入接收隊列中,等待軟中斷處理 中斷模式不用環(huán)形接收緩沖隊列
#endif
netdev->last_rx = jiffies;
next_desc:
rx_desc->status =0;
if(unlikely(cleaned_count >= E1000_RX_BUFFER_WRITE)){
adapter->alloc_rx_buf(adapter,rx_ring,cleaned_count); //在e1000_up中已經(jīng)調用了這個(gè)函數為環(huán)形緩沖區隊列中的每個(gè)緩沖區分配了sk_buff內存,但是如果接收到數據以后,調用netif_receive_skb(skb)向上提交數據以后,這段內存將始終被這個(gè)skb占用(直到上層處理完以后才會(huì )調用_kfree_skb釋放),換句話(huà)說(shuō),就是當前緩沖區必須重新申請分配sk_buff內存,為下一個(gè)數據作準備
cleaned_count = 0;
}
rx_desc = next_rxd;
buffer_info = next_buffer;
}
rx_ring->next_to_clean = i;
cleaned_count = E1000_DESC_UNUSED(rx_ring);
if(cleaned_count)
adapter->alloc_rx_buf(adapter,rx_ring,cleaned_count);
...
return cleaned;
}
static void e1000_alloc_rx_buffers(struct e1000_adapter *adapter,struct e1000_rx_ring *rx_ring,int cleaned_count){
struct net_device *netdev = adapter->netdev;
struct pci_dev *pdev = adapter->pdev;
struct e1000_rx_desc *rx_desc;
struct e1000_buffer *buffer_info;
struct sk_buff *skb;
unsigned int i;
unsigned int bufsz = adapter->rx_buffer_len+NET_IP_ALIGN;
i=rx_ring->next_to_use;
buffer_info = &rx_ring->buffer_info[i];
while (cleaned_count--){
skb = buffer_info ->skb;
if(skb){
....
}
skb = netdev_alloc_skb(netdev,bufsz); //skb緩存的分配
if(unlikely(!skb)){
adapter->alloc_rx_buff_failed++;
break;
}
skb_reserve(skb,NET_IP_ALIGN);
buffer_info->skb = skb;
buffer_info->length = adapter ->rx_buffer_len;
map_skb:
buffer_info->dma = pci_map_single(pdev,skb->data, adapter->rx_buffer_len,PCI_DMA_FROMDEVICE); //建立DMA映射,把每一個(gè)緩沖區skb->data都映射給了設備,緩存區描述符利用dma保存了每一次映射的地址
....
rx_desc = E1000_RX_DESC(*rx_ring, i);
rx_desc->buffer_addr = cpu_to_le64(buffer_info->dma);
if (unlikely(++i == rx_ring->count)) //達到環(huán)形緩沖區末尾
i =0 ;
buffer_info = &rx_ring->buffer_info[i];
}
if(likely(rx_ring->netx_to_use!=i)){
rx_ring->next_to_use = i;
if (unlikely(i-- == 0))
i = (rx_ring->count - 1);
...
}
}
簡(jiǎn)要流程
15:16:23 |
寫(xiě)入日志評論
若要添加評論,請使用您的 Windows Live ID 登錄(如果您使用過(guò) Hotmail、Messenger 或 Xbox LIVE,您就擁有 Windows Live ID)。
登錄還沒(méi)有 Windows Live ID 嗎?請
注冊引用通告
此日志的引用通告 URL 是:
http://sh-neo.spaces.live.com/blog/cns!1E3CA285E5F9E122!526.trak
引用此項的網(wǎng)絡(luò )日志