1. 程式人生 > >網絡卡驅動的資料包傳送接收

網絡卡驅動的資料包傳送接收

我們在許多網絡卡驅動中,都可以在網絡卡的中斷函式中見到這一過程。

但是,這一種方法,有一種重要的問題,就是大流量的資料來到,網絡卡會產生大量的中斷,核心在中斷上下文中,會浪費大量的資源來處理中斷本身。所以,一個問題是,“可不可以不使用中斷”,這就是輪詢技術,所謂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,enum dma_data_direction direction)
建立對映關係。
struct device *dev,描述一個裝置;
buffer:把哪個地址對映給裝置;也就是某一個skb——要對映全部,當然是做一個雙向連結串列的迴圈即可;
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緩衝區中的內容。

關於DMA對映的更多內容,可以參考《Linux裝置驅動程式》“記憶體對映和DMA”章節相關內容!

OK,有了這些知識,我們就可以來看e100的程式碼了,它跟上面講的步驟基本上一樣的——繞了這麼多圈子,就是想繞到e100上面了,呵呵!


在e100_open函式中,呼叫e100_up,我們前面分析它時,略過了一個重要的東東,就是環形緩衝區的建立,這一步,是通過
e100_rx_alloc_list函式呼叫完成的:

  1. staticint e100_rx_alloc_list(struct nic *nic)  
  2. {  
  3.         struct rx *rx;  
  4.         unsigned int i, count = nic->params.rfds.count;  
  5.         nic->rx_to_use = nic->rx_to_clean = NULL;  
  6.         nic->ru_running = RU_UNINITIALIZED;  
  7.         /*結構struct rx用來描述一個緩衝區節點,這裡分配了count個*/
  8.         if(!(nic->rxs = kmalloc(sizeof(struct rx) * count, GFP_ATOMIC)))  
  9.                 return -ENOMEM;  
  10.         memset(nic->rxs, 0, sizeof(struct rx) * count);  
  11.         /*雖然是連續分配的,不過還是遍歷它,建立雙向連結串列,然後為每一個rx的skb指標分員分配空間 
  12.         skb用來描述核心中的一個數據包,呵呵,說到重點了*/
  13.         for(rx = nic->rxs, i = 0; i < count; rx++, i++) {  
  14.                 rx->next = (i + 1 < count) ? rx + 1 : nic->rxs;  
  15.                 rx->prev = (i == 0) ? nic->rxs + count - 1 : rx - 1;  
  16.                 if(e100_rx_alloc_skb(nic, rx)) {                /*分配快取*/
  17.                         e100_rx_clean_list(nic);  
  18.                         return -ENOMEM;  
  19.                 }  
  20.         }  
  21.         nic->rx_to_use = nic->rx_to_clean = nic->rxs;  
  22.         nic->ru_running = RU_SUSPENDED;  
  23.         return 0;  
  24. }  


  1. #define RFD_BUF_LEN (sizeof(struct rfd) + VLAN_ETH_FRAME_LEN)
  2. staticinlineint e100_rx_alloc_skb(struct nic *nic, struct rx *rx)  
  3. {  
  4.         /*skb快取的分配,是通過呼叫系統函式dev_alloc_skb來完成的,它同核心棧中通常呼叫alloc_skb的區別在於, 
  5.         它是原子的,所以,通常在中斷上下文中使用*/
  6.         if(!(rx->skb = dev_alloc_skb(RFD_BUF_LEN + NET_IP_ALIGN)))  
  7.                 return -ENOMEM;  
  8.         /*初始化必要的成員 */
  9.         rx->skb->dev = nic->netdev;  
  10.         skb_reserve(rx->skb, NET_IP_ALIGN);  
  11.         /*這裡在資料區之前,留了一塊sizeof(struct rfd) 這麼大的空間,該結構的 
  12.         一個重要作用,用來儲存一些狀態資訊,比如,在接收資料之前,可以先通過 
  13.         它,來判斷是否真有資料到達等,諸如此類*/
  14.         memcpy(rx->skb->data, &nic->blank_rfd, sizeof(struct rfd));  
  15.         /*這是最關鍵的一步,建立DMA對映,把每一個緩衝區rx->skb->data都對映給了裝置,快取區節點 
  16.         rx利用dma_addr儲存了每一次對映的地址,這個地址後面會被用到*/
  17.         rx->dma_addr = pci_map_single(nic->pdev, rx->skb->data,  
  18.                 RFD_BUF_LEN, PCI_DMA_BIDIRECTIONAL);  
  19.         if(pci_dma_mapping_error(rx->dma_addr)) {  
  20.                 dev_kfree_skb_any(rx->skb);  
  21.                 rx->skb = 0;  
  22.                 rx->dma_addr = 0;  
  23.                 return -ENOMEM;  
  24.         }  
  25.         /* Link the RFD to end of RFA by linking previous RFD to 
  26.          * this one, and clearing EL bit of previous.  */
  27.         if(rx->prev->skb) {  
  28.                 struct rfd *prev_rfd = (struct rfd *)rx->prev->skb->data;  
  29.                 /*put_unaligned(val,ptr);用到把var放到ptr指標的地方,它能處理處理記憶體對齊的問題 
  30.                 prev_rfd是在緩衝區開始處儲存的一點空間,它的link成員,也儲存了對映後的地址*/
  31.                 put_unaligned(cpu_to_le32(rx->dma_addr),  
  32.                         (u32 *)&prev_rfd->link);  
  33.                 wmb();  
  34.                 prev_rfd->command &= ~cpu_to_le16(cb_el);  
  35.                 pci_dma_sync_single_for_device(nic->pdev, rx->prev->dma_addr,  
  36.                         sizeof(struct rfd), PCI_DMA_TODEVICE);  
  37.         }  
  38.         return 0;  
  39. }  

e100_rx_alloc_list函式在一個迴圈中,建立了環形緩衝區,並呼叫e100_rx_alloc_skb為每個緩衝區分配了空間,並做了
DMA對映。這樣,我們就可以來看接收資料的過程了。

前面我們講過,中斷函式中,呼叫netif_rx_schedule,表明使用輪詢技術,系統會在未來某一時刻,呼叫裝置的poll函式:

  1. staticint e100_poll(struct net_device *netdev, int *budget)  
  2. {  
  3.         struct nic *nic = netdev_priv(netdev);  
  4.         unsigned int work_to_do = min(netdev->quota, *budget);  
  5.         unsigned int work_done = 0;  
  6.         int tx_cleaned;  
  7.         e100_rx_clean(nic, &work_done, work_to_do);  
  8.         tx_cleaned = e100_tx_clean(nic);  
  9.         /* If no Rx and Tx cleanup work was done, exit polling mode. */
  10.         if((!tx_cleaned && (work_done == 0)) || !netif_running(netdev)) {  
  11.                 netif_rx_complete(netdev);  
  12.                 e100_enable_irq(nic);  
  13.                 return 0;  
  14.         }  
  15.         *budget -= work_done;  
  16.         netdev->quota -= work_done;  
  17.         return 1;  
  18. }  

目前,我們只關心rx,所以,e100_rx_clean函式就成了我們關注的對像,它用來從緩衝佇列中接收全部資料(這或許是取名為clean的原因吧!):

  1. staticinlinevoid e100_rx_clean(struct nic *nic, unsigned int *work_done,  
  2.         unsigned int work_to_do)  
  3. {  
  4.         struct rx *rx;  
  5.         int restart_required = 0;  
  6.         struct rx *rx_to_start = NULL;  
  7.         /* are we already rnr? then pay attention!!! this ensures that 
  8.          * the state machine progression never allows a start with a  
  9.          * partially cleaned list, avoiding a race between hardware 
  10.          * and rx_to_clean when in NAPI mode */
  11.         if(RU_SUSPENDED == nic->ru_running)  
  12.                 restart_required = 1;  
  13.         /* 函式最重要的工作,就是遍歷環形緩衝區,接收資料*/
  14.         for(rx = nic->rx_to_clean; rx->skb; rx = nic->rx_to_clean = rx->next) {  
  15.                 int err = e100_rx_indicate(nic, rx, work_done, work_to_do);  
  16.                 if(-EAGAIN == err) {  
  17.                         /* hit quota so have more work to do, restart once 
  18.                          * cleanup is complete */
  19.                         restart_required = 0;  
  20.                         break;  
  21.                 } elseif(-ENODATA == err)  
  22.                         break/* No more to clean */
  23.         }  
  24.         /* save our starting point as the place we'll restart the receiver */
  25.         if(restart_required)  
  26.                 rx_to_start = nic->rx_to_clean;  
  27.         /* Alloc new skbs to refill list */
  28.         for(rx = nic->rx_to_use; !rx->skb; rx = nic->rx_to_use = rx->next) {  
  29.                 if(unlikely(e100_rx_alloc_skb(nic, rx)))  
  30.                         break/* Better luck next time (see watchdog) */
  31.         }  
  32.         if(restart_required) {  
  33.                 // ack the rnr?
  34.                 writeb(stat_ack_rnr, &nic->csr->scb.stat_ack);  
  35.                 e100_start_receiver(nic, rx_to_start);  
  36.                 if(work_done)  
  37.                         (*work_done)++;  
  38.         }  
  39. }  


  1. staticinlineint e100_rx_indicate(struct nic *nic, struct rx *rx,  
  2.         unsigned int *work_done, unsigned int work_to_do)  
  3. {  
  4.         struct sk_buff *skb = rx->skb;  
  5.         struct rfd *rfd = (struct rfd *)skb->data;  
  6.         u16 rfd_status, actual_size;  
  7.         if(unlikely(work_done && *work_done >= work_to_do))  
  8.                 return -EAGAIN;  
  9.         /* 讀取資料之前,也就是取消DMA對映之前,需要先讀取cb_complete 狀態位, 
  10.         以確定資料是否真的準備好了,並且,rfd的actual_size中,也包含了真實的資料大小 
  11.         pci_dma_sync_single_for_cpu函式前面已經介紹過,它讓CPU在取消DMA對映之前,具備 
  12.         訪問DMA快取的能力*/
  13.         pci_dma_sync_single_for_cpu(nic->pdev, rx->dma_addr,  
  14.                 sizeof(struct rfd), PCI_DMA_FROMDEVICE);  
  15.         rfd_status = le16_to_cpu(rfd->status);  
  16.         DPRINTK(RX_STATUS, DEBUG, "status=0x%04X\n", rfd_status);  
  17.         /* If data isn't ready, nothing to indicate */
  18.         if(unlikely(!(rfd_status & cb_complete)))  
  19.                 return -ENODATA;  
  20.         /* Get actual data size */
  21.         actual_size = le16_to_cpu(rfd->actual_size) & 0x3FFF;  
  22.         if(unlikely(actual_size > RFD_BUF_LEN - sizeof(struct rfd)))  
  23.                 actual_size = RFD_BUF_LEN - sizeof(struct rfd);  
  24.         /* 取消對映,因為通過DMA,網絡卡已經把資料放在了主記憶體中,這裡一取消,也就意味著, 
  25.         CPU可以處理主記憶體中的資料了 */
  26.         pci_unmap_single(nic->pdev, rx->dma_addr,  
  27.                 RFD_BUF_LEN, PCI_DMA_FROMDEVICE);  
  28.         /* this allows for a fast restart without re-enabling interrupts */
  29.         if(le16_to_cpu(rfd->command) & cb_el)  
  30.                 nic->ru_running = RU_SUSPENDED;  
  31.         /*正確地設定data指標,因為最前面有一個sizeof(struct rfd)大小區域,跳過它*/
  32.         skb_reserve(skb, sizeof(struct rfd));  
  33.         /*更新skb的tail和len指標,也是就更新接收到這麼多資料的長度*/
  34.         skb_put(skb, actual_size);  
  35.         /*設定協議位*/
  36.         skb->protocol = eth_type_trans(skb, nic->netdev);  
  37.         if(unlikely(!(rfd_status & cb_ok))) {  
  38.                 /* Don't indicate if hardware indicates errors */
  39.                 nic->net_stats.rx_dropped++;