译|Monitoring and Tuning the Linux Networking Stack: Receiving Data(一)

简介: 译|Monitoring and Tuning the Linux Networking Stack: Receiving Data

TL;DR

本文解释了 Linux 内核的计算机如何接收数据包,以及当数据包从网络流向用户程序时,如何监视和调优网络栈的每个组件。

更新 我们已经发布了本文的姊妹篇:监控和调优 Linux 网络栈:发送数据

更新 查看 监控和调优 Linux 网络栈图解指南:接收数据,它为下面的内容添加了一些图表。

如果不阅读内核的源代码,不深入了解到底发生了什么,就不可能调优或监控 Linux 网络栈。

希望本文能给想做这方面工作的人提供参考。

特别感谢

特别感谢 Private Internet Access 的工作人员雇用我们,结合其他网络研究进行进一步研究,并慷慨地允许以研究为基础发布这些信息。

本文基于为 Private Internet Access 所做的工作,最初以 5 部分的系列文章的形式发表。

监控和调优 Linux 网络栈的一般建议

Linux 网络栈是复杂的,没有一刀切的监控或调优解决方案。 如果您真的想调优网络栈,您别无选择,只能投入大量的时间、精力和金钱来了解网络系统的各个部分是如何交互的。

理想情况下,您应该考虑在网络栈的每一层测量数据包丢弃。 这样您就可以确定并缩小需要调优的组件的范围。

这就是我认为许多运营商偏离轨道的地方:假设一组 sysctl 设置或 /proc 值可以简单地被大规模重用。在某些情况下,也许可以,但事实证明,整个系统是如此微妙和交织在一起,如果您希望有意义的监控或调优,您必须努力深入了解系统如何运作。否则,您可以直接使用默认设置,在必要的进一步优化(以及推导这些设置所需的投资)之前,已经足够好。

本文中提供的许多示例设置仅用于说明目的,并不是对某个配置或默认设置的推荐或反对。 在调整任何设置之前,您应该围绕您需要监控的内容制定一个参考框架,以注意到有意义的变化。

通过网络连接到计算机时调整网络设置是危险的;你很容易地把自己锁在外面,或者完全关闭你的网络。 不要在生产机器上调整这些设置;相反,如果可能的话,在新机器上进行调整,再投入生产中。

概览

作为参考,您可能需要手边有一份设备数据手册。 这篇文章将研究由 igb 设备驱动程序控制的 Intel I350 以太网控制器。 您可以找到该数据手册(警告:大型 PDF)供您参考

数据包从到达到套接字接收缓冲区的流程概览:

  1. 驱动程序已加载并初始化。
  2. 数据包从网络到达 NIC。
  3. 数据包被复制(通过 DMA)到内核内存中的环形缓冲区。
  4. 产生硬件中断通知系统知道数据包到达内存。
  5. 驱动程序调用 NAPI 启动轮询循环(如果尚未运行轮询循环)。
  6. ksoftirqd 进程运行在系统的每个 CPU 上。 它们在启动时注册。 ksoftirqd 进程调用设备驱动程序在初始化期间注册的 NAPI poll 函数,从环形缓冲区收取数据包。
  7. 环形缓冲区中已写入网络数据的内存区域被取消映射。
  8. DMA 到内存的数据以 “skb” 向上传递到网络层,以进行更多处理。
  9. 如果 packet steering 启用或 NIC 具有多个接收队列,则传入的网络数据帧将分布在多个CPU 中。
  10. 网络数据帧从队列传递到协议层。
  11. 协议层处理数据。
  12. 协议层添加数据到套接字关联的接收缓冲区。

整个流程将在以下各节中详细介绍。

下面检查的协议层是IP和UDP协议层。 本文提供的许多信息也将作为其他协议层的参考。

详细探讨

本文将探讨 Linux 3.13.0 版本内核,贯穿全文提供了 GitHub 代码链接和代码片段。

准确理解 Linux 内核如何接收数据包是非常复杂的。 我们需要仔细检查和理解网络驱动程序是如何工作的,以便更加清晰理解后面的网络栈部分。

本文将介绍 igb 网络驱动程序。 此驱动程序用于相对常见的服务器 NIC,即 Intel Ethernet Controller I350。 那么,让我们从理解 igb 网络驱动程序的工作原理开始。

网络设备驱动程序

初始化

驱动程序注册一个初始化函数,当驱动程序被加载时,内核会调用该函数。 此函数使用module_init 宏注册。

igb 初始化函数(igb_init_module)及其与 module_init 的注册可以在 drivers/net/ethernet/intel/igb/igb_main.c 中找到。

两者都非常简单明了:

/**
 *  igb_init_module - Driver Registration Routine
 *
 *  igb_init_module is the first routine called when the driver is
 *  loaded. All it does is register with the PCI subsystem.
 **/
static int __init igb_init_module(void)
{
  int ret;
  pr_info("%s - version %s\n", igb_driver_string, igb_driver_version);
  pr_info("%s\n", igb_copyright);
  /* ... */
  ret = pci_register_driver(&igb_driver);
  return ret;
}
module_init(igb_init_module);

初始化设备的大部分工作都是调用 pci_register_driver 完成的,我们将在下面看到。

PCI 初始化

英特尔 I350 网卡是一种 PCI express 设备。

PCI 设备通过 PCI 配置空间 中的一系列寄存器标识自己。

当设备驱动程序被编译时,会使用一个名为 MODULE_DEVICE_TABLE 的宏(来自 include/module.h)来导出一个 PCI 设备 ID 表,标识设备驱动程序可以控制的设备。该表注册为一个结构的一部分,我们稍后将看到。

内核使用此表来确定要加载哪个设备驱动程序来控制设备。

这就是操作系统如何确定哪些设备连接到系统,以及应该使用哪个驱动程序与设备通信。

此表和 igb 驱动程序的 PCI 设备 ID 位于 drivers/net/ethernet/intel/igb/igb_main.cdrivers/net/ethernet/intel/igb/e1000_hw.h

static DEFINE_PCI_DEVICE_TABLE(igb_pci_tbl) = {
  { PCI_VDEVICE(INTEL, E1000_DEV_ID_I354_BACKPLANE_1GBPS) },
  { PCI_VDEVICE(INTEL, E1000_DEV_ID_I354_SGMII) },
  { PCI_VDEVICE(INTEL, E1000_DEV_ID_I354_BACKPLANE_2_5GBPS) },
  { PCI_VDEVICE(INTEL, E1000_DEV_ID_I211_COPPER), board_82575 },
  { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_COPPER), board_82575 },
  { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_FIBER), board_82575 },
  { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_SERDES), board_82575 },
  { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_SGMII), board_82575 },
  { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_COPPER_FLASHLESS), board_82575 },
  { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_SERDES_FLASHLESS), board_82575 },
  /* ... */
};
MODULE_DEVICE_TABLE(pci, igb_pci_tbl);

如上一节所示,驱动程序的初始化函数会调用 pci_register_driver

这个函数注册一个指针结构。 大多数指针是函数指针,但 PCI 设备 ID 表也被注册。 内核使用驱动程序注册的函数启动 PCI 设备。

来自 drivers/net/ethernet/intel/igb/igb_main.c

static struct pci_driver igb_driver = {
  .name     = igb_driver_name,
  .id_table = igb_pci_tbl,
  .probe    = igb_probe,
  .remove   = igb_remove,
  /* ... */
};
PCI 探测

一旦通过 PCI ID 识别了设备,内核就可以选择适当的驱动程序来控制该设备。每个 PCI 驱动程序都在内核的 PCI 系统中注册了一个探测函数。内核为尚未被设备驱动程序认领的设备调用此函数。一旦设备被认领,不会再就该设备询问其他驱动程序。大多数驱动程序都有大量的代码运行,以使设备做好使用准备。所做的确切事情因驱动程序而异。

要执行的一些典型操作包括:

  1. 启用 PCI 设备。
  2. 请求内存范围和 IO 端口
  3. 设置 DMA 掩码。
  4. 注册驱动程序支持的 ethtool 函数(下面将详细描述)。
  5. 启动看门狗任务(例如,e1000e 有一个看门狗任务来检查硬件是否挂起)。
  6. 其他设备相关的内容,如替代方法或处理硬件特定的状况之类。
  7. 创建、初始化和注册 struct net_device_ops 结构。此结构包含指向打开设备、发送数据到网络、设置 MAC 地址等各种函数的函数指针。
  8. 创建、初始化和注册抽象 struct net_device,表示网络设备。

让我们快速看一下 igb 驱动程序中 igb_probe 函数的一些操作。

PCI 初始化一瞥

下面的 igb_probe 函数代码执行一些基本的 PCI 配置。 来自drivers/net/ethernet/intel/igb/igb_main.c

err = pci_enable_device_mem(pdev);
/* ... */
err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
/* ... */
err = pci_request_selected_regions(pdev, pci_select_bars(pdev,
           IORESOURCE_MEM),
           igb_driver_name);
pci_enable_pcie_error_reporting(pdev);
pci_set_master(pdev);
pci_save_state(pdev);

首先,设备使用 pci_enable_device_mem 进行初始化。这将唤醒设备(如果它处于挂起状态),启用内存资源等。

接下来,将设置 DMA 掩码。此设备可以读写 64 位内存地址,因此使用 DMA_BIT_MASK(64) 调用 dma_set_mask_and_coherent

调用 pci_request_selected_regions 保留内存区域,启用 PCI Express 高级错误报告(如果加载了 PCI AER 驱动程序),调用 pci_set_master 启用 DMA,并调用 pci_save_state 保存 PCI 配置空间。


目录
相关文章
|
10月前
|
运维 监控 网络协议
译|llustrated Guide to Monitoring and Tuning the Linux Networking Stack: Receiving Data
译|llustrated Guide to Monitoring and Tuning the Linux Networking Stack: Receiving Data
73 0
|
6月前
|
Linux
linux下的内存查看(virt,res,shr,data的意义)
linux下的内存查看(virt,res,shr,data的意义)
109 0
|
10月前
|
SQL 缓存 监控
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十一)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十一)
84 0
|
10月前
|
SQL 存储 缓存
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十)
207 1
|
10月前
|
缓存 监控 Linux
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(九)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(九)
138 0
|
10月前
|
监控 Linux 调度
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(八)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(八)
70 0
|
10月前
|
存储 Ubuntu Linux
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(七)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(七)
85 0
|
2天前
|
运维 网络协议 Linux
【专栏】运维工程师工作时最常用的 20 个 Linux 命令有哪些?建议收藏
【4月更文挑战第28天】本文介绍了运维工程师常用的20个Linux命令,包括`ls`、`cd`、`pwd`、`mkdir`、`rm`、`cp`、`mv`、`cat`、`more`、`less`、`head`、`tail`、`grep`、`find`、`chmod`、`chown`、`chgrp`、`ps`、`top`和`ifconfig`,帮助提升工作效率。此外,还提到了其他常用的命令如`df`、`free`、`tar`、`ssh`、`scp`、`ping`、`netstat`、`iptables`、`systemctl`、`hostname`等,建议运维人员掌握以应对各种运维场景。
|
15小时前
|
存储 Linux Shell
linux课程第二课------命令的简单的介绍2
linux课程第二课------命令的简单的介绍2
|
16小时前
|
安全 Linux C语言
linux课程第一课------命令的简单的介绍
linux课程第一课------命令的简单的介绍