前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >25 张图,拆解 Linux 网络包发送过程 (2)

25 张图,拆解 Linux 网络包发送过程 (2)

作者头像
桶哥
发布2021-06-17 20:32:43
1K0
发布2021-06-17 20:32:43
举报
文章被收录于专栏:PHP饭米粒PHP饭米粒

二、网卡启动准备

现在的服务器上的网卡一般都是支持多队列的。每一个队列上都是由一个 RingBuffer 表示的,开启了多队列以后的的网卡就会对应有多个 RingBuffer。

网卡在启动时最重要的任务之一就是分配和初始化 RingBuffer,理解了 RingBuffer 将会非常有助于后面我们掌握发送。因为今天的主题是发送,所以就以传输队列为例,我们来看下网卡启动时分配 RingBuffer 的实际过程。

在网卡启动的时候,会调用到 __igb_open 函数,RingBuffer 就是在这里分配的。

代码语言:javascript
复制
//file: drivers/net/ethernet/intel/igb/igb_main.c
static int __igb_open(struct net_device *netdev, bool resuming)
{
 struct igb_adapter *adapter = netdev_priv(netdev);

 //分配传输描述符数组
 err = igb_setup_all_tx_resources(adapter);

 //分配接收描述符数组
 err = igb_setup_all_rx_resources(adapter);

 //开启全部队列
 netif_tx_start_all_queues(netdev);
}

在上面 __igb_open 函数调用 igb_setup_all_tx_resources 分配所有的传输 RingBuffer, 调用 igb_setup_all_rx_resources 创建所有的接收 RingBuffer。

代码语言:javascript
复制
//file: drivers/net/ethernet/intel/igb/igb_main.c
static int igb_setup_all_tx_resources(struct igb_adapter *adapter)
{
 //有几个队列就构造几个 RingBuffer
 for (i = 0; i < adapter->num_tx_queues; i++) {
  igb_setup_tx_resources(adapter->tx_ring[i]);
 }
}

真正的 RingBuffer 构造过程是在 igb_setup_tx_resources 中完成的。

代码语言:javascript
复制
//file: drivers/net/ethernet/intel/igb/igb_main.c
int igb_setup_tx_resources(struct igb_ring *tx_ring)
{
 //1.申请 igb_tx_buffer 数组内存
 size = sizeof(struct igb_tx_buffer) * tx_ring->count;
 tx_ring->tx_buffer_info = vzalloc(size);

 //2.申请 e1000_adv_tx_desc DMA 数组内存
 tx_ring->size = tx_ring->count * sizeof(union e1000_adv_tx_desc);
 tx_ring->size = ALIGN(tx_ring->size, 4096);
 tx_ring->desc = dma_alloc_coherent(dev, tx_ring->size,
        &tx_ring->dma, GFP_KERNEL);

 //3.初始化队列成员
 tx_ring->next_to_use = 0;
 tx_ring->next_to_clean = 0;
}

从上述源码可以看到,实际上一个 RingBuffer 的内部不仅仅是一个环形队列数组,而是有两个。

1)igb_tx_buffer 数组:这个数组是内核使用的,通过 vzalloc 申请的。 2)e1000_adv_tx_desc 数组:这个数组是网卡硬件使用的,硬件是可以通过 DMA 直接访问这块内存,通过 dma_alloc_coherent 分配。

这个时候它们之间还没有啥联系。将来在发送的时候,这两个环形数组中相同位置的指针将都将指向同一个 skb。这样,内核和硬件就能共同访问同样的数据了,内核往 skb 里写数据,网卡硬件负责发送。

最后调用 netif_tx_start_all_queues 开启队列。另外,对于硬中断的处理函数 igb_msix_ring 其实也是在 __igb_open 中注册的。

三、accept 创建新 socket

在发送数据之前,我们往往还需要一个已经建立好连接的 socket。

我们就以开篇服务器缩微源代码中提到的 accept 为例,当 accept 之后,进程会创建一个新的 socket 出来,然后把它放到当前进程的打开文件列表中,专门用于和对应的客户端通信。

假设服务器进程通过 accept 和客户端建立了两条连接,我们来简单看一下这两条连接和进程的关联关系。

其中代表一条连接的 socket 内核对象更为具体一点的结构图如下。

为了避免喧宾夺主,accept 详细的源码过程这里就不介绍了,感兴趣请参考 《图解 | 深入揭秘 epoll 是如何实现 IO 多路复用的!》。一文中的第一部分。

今天我们还是把重点放到数据发送过程上, 下篇我们继续

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-05-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PHP饭米粒 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 二、网卡启动准备
  • 三、accept 创建新 socket
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档