RealTek 8169/8168/8101的驱动代码,一共就一个文件(drivers/net/r8169.c),而且总代码行也就5千行左右,很适合新手入门学习。
而像intel e1000e的驱动,包含了较多的文件(总入口文件:drivers/net/e1000e/netdev.c),总代码行至少在1万行以上。
先来看看r8169的相关代码:
rtl8169_open中,用dma_alloc_coherent为网卡的接收发送申请了描述符环。
至于接收方向,与每个描述符对应的存放报文数据的buffer,是用kmalloc_node申请的,然后通过dma_map_single(此函数返回值为物理地址)做dma映射。
值得注意的是,此处调用kmalloc_node时,指定了从网卡所属的numa node申请内存。这是对性能的考虑。
但是,驱动在从硬件接收报文时,却做了一次报文拷贝,这对性能很不利。见rtl8169_rx_interrupt函数,其内部调用了rtl8169_try_rx_copy,其代码如下。
static struct sk_buff *rtl8169_try_rx_copy(void *data,
struct rtl8169_private *tp,
int pkt_size,
dma_addr_t addr)
{
struct sk_buff *skb;
struct device *d = &tp->pci_dev->dev;
data = rtl8169_align(data);
dma_sync_single_for_cpu(d, addr, pkt_size, DMA_FROM_DEVICE);
prefetch(data);
skb = netdev_alloc_skb_ip_align(tp->dev, pkt_size);
if (skb)
memcpy(skb->data, data, pkt_size);
dma_sync_single_for_device(d, addr, pkt_size, DMA_FROM_DEVICE);
return skb;
}
netdev_alloc_skb_ip_align会申请一个sk_buff结构,同时申请存放报文数据的buffer空间,并将他们关联起来。
再来看看intel e1000e的驱动代码。
e1000_clean_rx_irq中,有如下代码块。可见只有对于较小的报文,驱动才会做拷贝。
/*
* code added for copybreak, this should improve
* performance for small packets with large amounts
* of reassembly being done in the stack
*/
if (length < copybreak) {
struct sk_buff *new_skb =
netdev_alloc_skb_ip_align(netdev, length);
if (new_skb) {
skb_copy_to_linear_data_offset(new_skb,
-NET_IP_ALIGN,
(skb->data -
NET_IP_ALIGN),
(length +
NET_IP_ALIGN));
/* save the skb in buffer_info as good */
buffer_info->skb = skb;
skb = new_skb;
}
/* else just continue with the old one */
}
/* end copybreak code */
那他是怎么注入报文数据buffer的呢?见e1000_alloc_rx_buffers函数。
其调用netdev_alloc_skb_ip_align申请skb描述符及报文数据buffer,然后将报文的数据buffer做dma映射。见下面的代码块:
skb = netdev_alloc_skb_ip_align(netdev, bufsz);
if (!skb) {
/* Better luck next round */
adapter->alloc_rx_buff_failed++;
break;
}
buffer_info->skb = skb;
map_skb:
buffer_info->dma = dma_map_single(&pdev->dev, skb->data,
adapter->rx_buffer_len,
DMA_FROM_DEVICE);
if (dma_mapping_error(&pdev->dev, buffer_info->dma)) {
dev_err(&pdev->dev, "Rx DMA map failed\n");
adapter->rx_dma_failed++;
break;
}
rx_desc = E1000_RX_DESC(*rx_ring, i);
rx_desc->buffer_addr = cpu_to_le64(buffer_info->dma);
这样的设计,减少了一次报文的拷贝。但是,netdev_alloc_skb_ip_align没有参数指定从哪个numa node申请内存。
因此,如果没有相应的机制,保证申请的内存来自网卡所在的numa节点,则又会遇到跨numa node访存导致的性能损失。