在驱动函数里,将 skb 会挂到 RingBuffer上,驱动调用完毕后,数据包将真正从网卡发送出去。
![](http://imgq8.q578.com/ef/0602/db7a01514a50f6ce.jpg)
我们来看看实际的源码:
//file: net/core/dev.c
int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,
struct netdev_queue *txq)
{
//获取设备的回调函数集合 ops
const struct net_device_ops *ops = dev->netdev_ops;
//获取设备支持的功能列表
features = netif_skb_features(skb);
//调用驱动的 ops 里面的发送回调函数 ndo_start_xmit 将数据包传给网卡设备
skb_len = skb->len;
rc = ops->ndo_start_xmit(skb, dev);
}
其中 ndo_start_xmit 是网卡驱动要实现的一个函数,是在 net_device_ops 中定义的。
//file: include/linux/netdevice.h
struct net_device_ops {
netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb,
struct net_device *dev);
}
在 igb 网卡驱动源码中,我们找到了。
//file: drivers/net/ethernet/intel/igb/igb_main.c
static const struct net_device_ops igb_netdev_ops = {
.ndo_open = igb_open,
.ndo_stop = igb_close,
.ndo_start_xmit = igb_xmit_frame,
...
};
也就是说,对于网络设备层定义的 ndo_start_xmit, igb 的实现函数是 igb_xmit_frame。这个函数是在网卡驱动初始化的时候被赋值的。具体初始化过程参见《图解Linux网络包接收过程》一文中的 2.4 节,网卡驱动初始化。
所以在上面网络设备层调用 ops->ndo_start_xmit 的时候,会实际上进入 igb_xmit_frame 这个函数中。我们进入这个函数来看看驱动程序是如何工作的。
//file: drivers/net/ethernet/intel/igb/igb_main.c
static netdev_tx_t igb_xmit_frame(struct sk_buff *skb,
struct net_device *netdev)
{
......
return igb_xmit_frame_ring(skb, igb_tx_queue_mapping(adapter, skb));
}
netdev_tx_t igb_xmit_frame_ring(struct sk_buff *skb,
struct igb_ring *tx_ring)
{
//获取TX Queue 中下一个可用缓冲区信息
first = &tx_ring->tx_buffer_info[tx_ring->next_to_use];
first->skb = skb;
first->bytecount = skb->len;
first->gso_segs = 1;
//igb_tx_map 函数准备给设备发送的数据。
igb_tx_map(tx_ring, first, hdr_len);
}
在这里从网卡的发送队列的 RingBuffer 中取下来一个元素,并将 skb 挂到元素上。
![](http://imgq8.q578.com/ef/0602/cfae8c787fe17567.jpg)
igb_tx_map 函数处理将 skb 数据映射到网卡可访问的内存 DMA 区域。
//file: drivers/net/ethernet/intel/igb/igb_main.c
static void igb_tx_map(struct igb_ring *tx_ring,
struct igb_tx_buffer *first,
const u8 hdr_len)
{
//获取下一个可用描述符指针
tx_desc = IGB_TX_DESC(tx_ring, i);
//为 skb->data 构造内存映射,以允许设备通过 DMA 从 RAM 中读取数据
dma = dma_map_single(tx_ring->dev, skb->data, size, DMA_TO_DEVICE);
//遍历该数据包的所有分片,为 skb 的每个分片生成有效映射
for (frag = &skb_shinfo(skb)->frags[0];; frag++) {
tx_desc->read.buffer_addr = cpu_to_le64(dma);
tx_desc->read.cmd_type_len = ...;
tx_desc->read.olinfo_status = 0;
}
//设置最后一个descriptor
cmd_type |= size | IGB_TXD_DCMD;
tx_desc->read.cmd_type_len = cpu_to_le32(cmd_type);
/* Force memory writes to complete before letting h/w know there
* are new descriptors to fetch
*/
wmb();
}
当所有需要的描述符都已建好,且 skb 的所有数据都映射到 DMA 地址后,驱动就会进入到它的最后一步,触发真实的发送。
4.8 发送完成硬中断
当数据发送完成以后,其实工作并没有结束。因为内存还没有清理。当发送完成的时候,网卡设备会触发一个硬中断来释放内存。
在《图解Linux网络包接收过程》 一文中的 3.1 和 3.2 小节,我们详细讲述过硬中断和软中断的处理过程。
在发送完成硬中断里,会执行 RingBuffer 内存的清理工作,如图。
![](http://imgq8.q578.com/ef/0602/c2eefba44998cce1.jpg)