• Linux内核数据包处理流程-数据包接收(2)[转载]


    四、网卡的数据接收

    内核如何从网卡接受数据,传统的经典过程:
    1、数据到达网卡;
    2
    、网卡产生一个中断给内核;
    3
    、内核使用I/O指令,从网卡I/O区域中去读取数据;

    我们在许多网卡驱动中,都可以在网卡的中断函数中见到这一过程。

    但是,这一种方法,有一种重要的问题,就是大流量的数据来到,网卡会产生大量的中断,内核在中断上下文中,会浪费大量的资源来处理中断本身。所以,一个问题是,可不可以不使用中断,这就是轮询技术,所谓NAPI技术,说来也不神秘,就是说,内核屏蔽中断,然后隔一会儿就去问网卡,你有没有数据啊?”……

    从这个描述本身可以看到,哪果数据量少,轮询同样占用大量的不必要的CPU资源,大家各有所长吧,呵呵……

    OK
    ,另一个问题,就是从网卡的I/O区域,包括I/O寄存器或I/O内存中去读取数据,这都要CPU去读,也要占用CPU资源,“CPUI/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
    ,描述一个设备;
    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函数调用完成的:

    [cpp] view plain copy

    1.  static int e100_rx_alloc_list(struct nic *nic)  

    2.  {  

    3.          struct rx *rx;  

    4.          unsigned int i, count = nic->params.rfds.count;  

    5.    

    6.          nic->rx_to_use = nic->rx_to_clean = NULL;  

    7.          nic->ru_running = RU_UNINITIALIZED;  

    8.    

    9.           

    10.        if(!(nic->rxs = kmalloc(sizeof(struct rx) * count, GFP_ATOMIC)))  

    11.                return -ENOMEM;  

    12.        memset(nic->rxs, 0, sizeof(struct rx) * count);  

    13.  

    14.         

    15.        for(rx = nic->rxs, i = 0; i < count; rx++, i++) {  

    16.                rx->next = (i + 1 < count) ? rx + 1 : nic->rxs;  

    17.                rx->prev = (i == 0) ? nic->rxs + count - 1 : rx - 1;  

    18.                if(e100_rx_alloc_skb(nic, rx)) {                 

    19.                        e100_rx_clean_list(nic);  

    20.                        return -ENOMEM;  

    21.                }  

    22.        }  

    23.  

    24.        nic->rx_to_use = nic->rx_to_clean = nic->rxs;  

    25.        nic->ru_running = RU_SUSPENDED;  

    26.  

    27.        return 0;  

    28.}  

     

    [cpp] view plain copy

    1.  #define RFD_BUF_LEN (sizeof(struct rfd) + VLAN_ETH_FRAME_LEN)  

    2.  static inline int e100_rx_alloc_skb(struct nic *nic, struct rx *rx)  

    3.  {  

    4.           

    5.          if(!(rx->skb = dev_alloc_skb(RFD_BUF_LEN + NET_IP_ALIGN)))  

    6.                  return -ENOMEM;  

    7.    

    8.           

    9.          rx->skb->dev = nic->netdev;  

    10.        skb_reserve(rx->skb, NET_IP_ALIGN);  

    11.         

    12.        memcpy(rx->skb->#, &nic->blank_rfd, sizeof(struct rfd));  

    13.         

    14.        rx->dma_addr = pci_map_single(nic->pdev, rx->skb->#,  

    15.                RFD_BUF_LEN, PCI_DMA_BIDIRECTIONAL);  

    16.  

    17.        if(pci_dma_mapping_error(rx->dma_addr)) {  

    18.                dev_kfree_skb_any(rx->skb);  

    19.                rx->skb = 0;  

    20.                rx->dma_addr = 0;  

    21.                return -ENOMEM;  

    22.        }  

    23.  

    24.         

    25.        if(rx->prev->skb) {  

    26.                struct rfd *prev_rfd = (struct rfd *)rx->prev->skb->#;  

    27.                 

    28.                put_unaligned(cpu_to_le32(rx->dma_addr),  

    29.                        (u32 *)&prev_rfd->link);  

    30.                wmb();  

    31.                prev_rfd->command &= ~cpu_to_le16(cb_el);  

    32.                pci_dma_sync_single_for_device(nic->pdev, rx->prev->dma_addr,  

    33.                        sizeof(struct rfd), PCI_DMA_TODEVICE);  

    34.        }  

    35.  

    36.        return 0;  

    37.}  


    e100_rx_alloc_list
    函数在一个循环中,建立了环形缓冲区,并调用e100_rx_alloc_skb为每个缓冲区分配了空间,并做了
    DMA
    映射。这样,我们就可以来看接收数据的过程了。

    前面我们讲过,中断函数中,调用netif_rx_schedule,表明使用轮询技术,系统会在未来某一时刻,调用设备的poll函数

    [cpp] view plain copy

    1.  static int 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.    

    8.          e100_rx_clean(nic, &work_done, work_to_do);  

    9.          tx_cleaned = e100_tx_clean(nic);  

    10.  

    11.         

    12.        if((!tx_cleaned && (work_done == 0)) || !netif_running(netdev)) {  

    13.                netif_rx_complete(netdev);  

    14.                e100_enable_irq(nic);  

    15.                return 0;  

    16.        }  

    17.  

    18.        *budget -= work_done;  

    19.        netdev->quota -= work_done;  

    20.  

    21.        return 1;  

    22.}  


    目前,我们只关心rx,所以,e100_rx_clean函数就成了我们关注的对像,它用来从缓冲队列中接收全部数据(这或许是取名为clean的原因吧!

    [cpp] view plain copy

    1.  static inline void 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.    

    8.           

    9.          if(RU_SUSPENDED == nic->ru_running)  

    10.                restart_required = 1;  

    11.  

    12.         

    13.        for(rx = nic->rx_to_clean; rx->skb; rx = nic->rx_to_clean = rx->next) {  

    14.                int err = e100_rx_indicate(nic, rx, work_done, work_to_do);  

    15.                if(-EAGAIN == err) {  

    16.                         

    17.                        restart_required = 0;  

    18.                        break;  

    19.                } else if(-ENO# == err)  

    20.                        break;  

    21.        }  

    22.  

    23.         

    24.        if(restart_required)  

    25.                rx_to_start = nic->rx_to_clean;  

    26.  

    27.         

    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;  

    31.        }  

    32.  

    33.        if(restart_required) {  

    34.                // ack the rnr?  

    35.                writeb(stat_ack_rnr, &nic->csr->scb.stat_ack);  

    36.                e100_start_receiver(nic, rx_to_start);  

    37.                if(work_done)  

    38.                        (*work_done)++;  

    39.        }  

    40.}  

     

    [cpp] view plain copy

    1.  static inline int 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->#;  

    6.          u16 rfd_status, actual_size;  

    7.    

    8.          if(unlikely(work_done && *work_done >= work_to_do))  

    9.                  return -EAGAIN;  

    10.  

    11.         

    12.        pci_dma_sync_single_for_cpu(nic->pdev, rx->dma_addr,  

    13.                sizeof(struct rfd), PCI_DMA_FROMDEVICE);  

    14.        rfd_status = le16_to_cpu(rfd->status);  

    15.  

    16.        DPRINTK(RX_STATUS, DEBUG, "status=0x%04X ", rfd_status);  

    17.  

    18.         

    19.        if(unlikely(!(rfd_status & cb_complete)))  

    20.                return -ENO#;  

    21.  

    22.         

    23.        actual_size = le16_to_cpu(rfd->actual_size) & 0x3FFF;  

    24.        if(unlikely(actual_size > RFD_BUF_LEN - sizeof(struct rfd)))  

    25.                actual_size = RFD_BUF_LEN - sizeof(struct rfd);  

    26.  

    27.         

    28.        pci_unmap_single(nic->pdev, rx->dma_addr,  

    29.                RFD_BUF_LEN, PCI_DMA_FROMDEVICE);  

    30.  

    31.         

    32.        if(le16_to_cpu(rfd->command) & cb_el)  

    33.                nic->ru_running = RU_SUSPENDED;  

    34.         

    35.         

    36.        skb_reserve(skb, sizeof(struct rfd));  

    37.         

    38.        skb_put(skb, actual_size);  

    39.         

    40.        skb->protocol = eth_type_trans(skb, nic->netdev);  

    41.  

    42.        if(unlikely(!(rfd_status & cb_ok))) {  

    43.                 

    44.                nic->net_stats.rx_dropped++;  

    45.                dev_kfree_skb_any(skb);  

    46.        } else if(actual_size > nic->netdev->mtu + VLAN_ETH_HLEN) {  

    47.                 

    48.                nic->rx_over_length_errors++;  

    49.                nic->net_stats.rx_dropped++;  

    50.                dev_kfree_skb_any(skb);  

    51.        } else {  

    52.                 

    53.                nic->net_stats.rx_packets++;  

    54.                nic->net_stats.rx_bytes += actual_size;  

    55.                nic->netdev->last_rx = jiffies;  

    56.                netif_receive_skb(skb);  

    57.                if(work_done)  

    58.                        (*work_done)++;  

    59.        }  

    60.  

    61.        rx->skb = NULL;  

    62.  

    63.        return 0;  

    64.}  


    网卡驱动执行到这里,数据接收的工作,也就处理完成了。但是,使用这一种方法的驱动,省去了网络栈中一个重要的内容,就是
    队列层,让我们来看看,传统中断接收数据包模式下,使用netif_rx函数调用,又会发生什么

  • 相关阅读:
    不同的二叉搜索树
    二叉树展开为链表
    二叉树的中序遍历
    二叉树的直径
    树系列之对称二叉树
    从前序与中序遍历序列构造二叉树
    字符串反转
    旋转图像---二维矩阵
    双指针---最接近的三数之和
    Egret 小游戏实战教程 跳一跳(搬运二)
  • 原文地址:https://www.cnblogs.com/listenerln/p/6393047.html
Copyright © 2020-2023  润新知