Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,56 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/networking/alias.rst
:翻译:
邱禹潭 Qiu Yutan <qiu.yutan@zte.com.cn>
:校译:
======
IP别名
======
IP别名是管理每个接口存在多个IP地址/子网掩码的一种过时方法。
虽然更新的工具如iproute2支持每个接口多个地址/前缀,
但为了向后兼容性,别名仍被支持。
别名通过在使用 ifconfig 时在接口名后添加冒号和一个字符串来创建。
这个字符串通常是数字,但并非必须。
别名创建
========
别名的创建是通过“特殊的”接口命名机制完成的:例如,
要为eth0创建一个 200.1.1.1 的别名...
::
# ifconfig eth0:0 200.1.1.1 等等
~~ -> 请求为eth0创建别名#0(如果尚不存在)
该命令也会设置相应的路由表项。请注意:路由表项始终指向基础接口。
别名删除
========
通过关闭别名即可将其删除::
# ifconfig eth0:0 down
~~~~~~~~~~ -> 将删除别名
别名(重新)配置
================
别名不是真实的设备,但程序应该能够正常配置和引用它们(ifconfig、route等)。
与主设备的关系
==============
如果基础设备被关闭,则其上添加的所有别名也将被删除。
@@ -0,0 +1,176 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/networking/generic-hdlc.rst
:翻译:
孙渔喜 Sun yuxi <sun.yuxi@zte.com.cn>
==========
通用HDLC层
==========
Krzysztof Halasa <khc@pm.waw.pl>
通用HDLC层当前支持以下协议:
1. 帧中继(支持ANSI、CCITT、Cisco及无LMI模式)
- 常规(路由)接口和以太网桥接(以太网设备仿真)接口
可共享同一条PVC。
- 支持ARP(内核暂不支持InARP,但可通过实验性用户空间守护程序实现,
下载地址:http://www.kernel.org/pub/linux/utils/net/hdlc/)。
2. 原始HDLC —— 支持IP(IPv4)接口或以太网设备仿真
3. Cisco HDLC
4. PPP
5. X.25(使用X.25协议栈)
通用HDLC仅作为协议驱动 - 必须配合具体硬件的底层驱动
才能运行。
以太网设备仿真(使用HDLC或帧中继PVC)兼容IEEE 802.1Q(VLAN)和
802.1D(以太网桥接)。
请确保已加载 hdlc.o 和硬件驱动程序。系统将为每个WAN端口创建一个
"hdlc"网络设备(如hdlc0等)。您需要使用"sethdlc"工具,可从以下
地址获取:
http://www.kernel.org/pub/linux/utils/net/hdlc/
编译 sethdlc.c 工具::
gcc -O2 -Wall -o sethdlc sethdlc.c
请确保使用与您内核版本匹配的 sethdlc 工具。
使用 sethdlc 工具设置物理接口、时钟频率、HDLC 模式,
若使用帧中继还需添加所需的 PVC。
通常您需要执行类似以下命令::
sethdlc hdlc0 clock int rate 128000
sethdlc hdlc0 cisco interval 10 timeout 25
或::
sethdlc hdlc0 rs232 clock ext
sethdlc hdlc0 fr lmi ansi
sethdlc hdlc0 create 99
ifconfig hdlc0 up
ifconfig pvc0 localIP pointopoint remoteIP
在帧中继模式下,请先启用主hdlc设备(不分配IP地址),再
使用pvc设备。
接口设置选项:
* v35 | rs232 | x21 | t1 | e1
- 当网卡支持软件可选接口时,可为指定端口设置物理接口
loopback
- 启用硬件环回(仅用于测试)
* clock ext
- RX与TX时钟均使用外部时钟源
* clock int
- RX与TX时钟均使用内部时钟源
* clock txint
- RX时钟使用外部时钟源,TX时钟使用内部时钟源
* clock txfromrx
- RX时钟使用外部时钟源,TX时钟从RX时钟派生
* rate
- 设置时钟速率(仅适用于"int"或"txint"时钟模式)
设置协议选项:
* hdlc - 设置原始HDLC模式(仅支持IP协议)
nrz / nrzi / fm-mark / fm-space / manchester - 传输编码选项
no-parity / crc16 / crc16-pr0 (预设零值的CRC16) / crc32-itu
crc16-itu (使用ITU-T多项式的CRC16) / crc16-itu-pr0 - 校验方式选项
* hdlc-eth - 使用HDLC进行以太网设备仿真. 校验和编码方式同上
as above.
* cisco - 设置Cisco HDLC模式(支持IP、IPv6和IPX协议)
interval - 保活数据包发送间隔(秒)
timeout - 未收到保活数据包的超时时间(秒),超过此时长将判定
链路断开
* ppp - 设置同步PPP模式
* x25 - 设置X.25模式
* fr - 帧中继模式
lmi ansi / ccitt / cisco / none - LMI(链路管理)类型
dce - 将帧中继设置为DCE(网络侧)LMI模式(默认为DTE用户侧)。
此设置与时钟无关!
- t391 - 链路完整性验证轮询定时器(秒)- 用户侧
- t392 - 轮询验证定时器(秒)- 网络侧
- n391 - 全状态轮询计数器 - 用户侧
- n392 - 错误阈值 - 用户侧和网络侧共用
- n393 - 监控事件计数 - 用户侧和网络侧共用
帧中继专用命令:
* create n | delete n - 添加/删除DLCI编号为n的PVC接口。
新创建的接口将命名为pvc0、pvc1等。
* create ether n | delete ether n - 添加/删除用于以太网
桥接帧的设备设备将命名为pvceth0、pvceth1等。
板卡特定问题
------------
n2.o 和 c101.o 驱动模块需要参数才能工作::
insmod n2 hw=io,irq,ram,ports[:io,irq,...]
示例::
insmod n2 hw=0x300,10,0xD0000,01
或::
insmod c101 hw=irq,ram[:irq,...]
示例::
insmod c101 hw=9,0xdc000
若直接编译进内核,这些驱动需要通过内核(命令行)参数配置::
n2.hw=io,irq,ram,ports:...
或::
c101.hw=irq,ram:...
若您的N2、C101或PLX200SYN板卡出现问题,可通过"private"
命令查看端口数据包描述符环(显示在内核日志中)
sethdlc hdlc0 private
硬件驱动需使用#define DEBUG_RINGS编译选项构建。
在提交错误报告时附上这些信息将很有帮助。如在使用过程中遇
到任何问题,请随时告知。
获取补丁和其他信息,请访问:
<http://www.kernel.org/pub/linux/utils/net/hdlc/>.
@@ -0,0 +1,158 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/networking/index.rst
:翻译:
王亚鑫 Wang Yaxin <wang.yaxin@zte.com.cn>
:校译:
网络
====
有关网络设备(netdev)开发过程的详细指南,请参考::ref:`netdev-FAQ`
目录:
.. toctree::
:maxdepth: 1
msg_zerocopy
napi
vxlan
netif-msg
xfrm_proc
netmem
alias
mptcp-sysctl
generic-hdlc
timestamping
Todolist:
* af_xdp
* bareudp
* batman-adv
* can
* can_ucan_protocol
* device_drivers/index
* diagnostic/index
* dsa/index
* devlink/index
* ethtool-netlink
* ieee802154
* iso15765-2
* j1939
* kapi
* failover
* net_dim
* net_failover
* page_pool
* phy
* sfp-phylink
* bridge
* snmp_counter
* checksum-offloads
* segmentation-offloads
* scaling
* tls
* tls-offload
* tls-handshake
* nfc
* 6lowpan
* 6pack
* arcnet-hardware
* arcnet
* atm
* ax25
* bonding
* cdc_mbim
* dccp
* dctcp
* devmem
* dns_resolver
* driver
* eql
* fib_trie
* filter
* generic_netlink
* netlink_spec/index
* gen_stats
* gtp
* ila
* ioam6-sysctl
* ip_dynaddr
* ipsec
* ip-sysctl
* ipv6
* ipvlan
* ipvs-sysctl
* kcm
* l2tp
* lapb-module
* mac80211-injection
* mctp
* mpls-sysctl
* mptcp
* multiqueue
* multi-pf-netdev
* net_cachelines/index
* netconsole
* netdev-features
* netdevices
* netfilter-sysctl
* nexthop-group-resilient
* nf_conntrack-sysctl
* nf_flowtable
* oa-tc6-framework
* openvswitch
* operstates
* packet_mmap
* phonet
* phy-link-topology
* pktgen
* plip
* ppp_generic
* proc_net_tcp
* pse-pd/index
* radiotap-headers
* rds
* regulatory
* representors
* rxrpc
* sctp
* secid
* seg6-sysctl
* smc-sysctl
* sriov
* statistics
* strparser
* switchdev
* sysfs-tagging
* tc-actions-env-rules
* tc-queue-filters
* tcp_ao
* tcp-thin
* team
* tipc
* tproxy
* tuntap
* udplite
* vrf
* x25
* x25-iface
* xfrm_device
* xfrm_sync
* xfrm_sysctl
* xdp-rx-metadata
* xsk-tx-metadata
.. only:: subproject and html
Indices
=======
* :ref:`genindex`
@@ -0,0 +1,139 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/networking/mptcp-sysctl.rst
:翻译:
孙渔喜 Sun yuxi <sun.yuxi@zte.com.cn>
================
MPTCP Sysfs 变量
================
/proc/sys/net/mptcp/* Variables
===============================
add_addr_timeout - INTEGER (秒)
设置ADD_ADDR控制消息的重传超时时间。当MPTCP对端未确认
先前的ADD_ADDR消息时,将在该超时时间后重新发送。
默认值与TCP_RTO_MAX相同。此为每个命名空间的sysctl参数。
默认值:120
allow_join_initial_addr_port - BOOLEAN
控制是否允许对端向初始子流使用的IP地址和端口号发送加入
请求(1表示允许)。此参数会设置连接时发送给对端的标志位,
并决定是否接受此类加入请求。
通过ADD_ADDR通告的地址不受此参数影响。
此为每个命名空间的sysctl参数。
默认值:1
available_path_managers - STRING
显示已注册的可用路径管理器选项。可能有更多路径管理器可用
但尚未加载。
available_schedulers - STRING
显示已注册的可用调度器选项。可能有更多数据包调度器可用
但尚未加载。
blackhole_timeout - INTEGER (秒)
当发生MPTCP防火墙黑洞问题时,初始禁用活跃MPTCP套接字上MPTCP
功能的时间(秒)。如果在重新启用MPTCP后立即检测到更多黑洞问题,
此时间段将呈指数增长;当黑洞问题消失时,将重置为初始值。
设置为0可禁用黑洞检测功能。此为每个命名空间的sysctl参数。
默认值:3600
checksum_enabled - BOOLEAN
控制是否启用DSS校验和功能。
当值为非零时可启用DSS校验和。此为每个命名空间的sysctl参数。
默认值:0
close_timeout - INTEGER (seconds)
设置"先断后连"超时时间:在未调用close或shutdown系统调用时,
MPTCP套接字将在最后一个子流移除后保持当前状态达到该时长,才
会转为TCP_CLOSE状态。
默认值与TCP_TIMEWAIT_LEN相同。此为每个命名空间的sysctl参数。
默认值:60
enabled - BOOLEAN
控制是否允许创建MPTCP套接字。
当值为1时允许创建MPTCP套接字。此为每个命名空间的sysctl参数。
默认值:1(启用)
path_manager - STRING
设置用于每个新MPTCP套接字的默认路径管理器名称。内核路径管理将
根据通过MPTCP netlink API配置的每个命名空间值来控制子流连接
和地址通告。用户空间路径管理将每个MPTCP连接的子流连接决策和地
址通告交由特权用户空间程序控制,代价是需要更多netlink流量来
传播所有相关事件和命令。
此为每个命名空间的sysctl参数。
* "kernel" - 内核路径管理器
* "userspace" - 用户空间路径管理器
默认值:"kernel"
pm_type - INTEGER
设置用于每个新MPTCP套接字的默认路径管理器类型。内核路径管理将
根据通过MPTCP netlink API配置的每个命名空间值来控制子流连接
和地址通告。用户空间路径管理将每个MPTCP连接的子流连接决策和地
址通告交由特权用户空间程序控制,代价是需要更多netlink流量来
传播所有相关事件和命令。
此为每个命名空间的sysctl参数。
自v6.15起已弃用,请改用path_manager参数。
* 0 - 内核路径管理器
* 1 - 用户空间路径管理器
默认值:0
scheduler - STRING
选择所需的调度器类型。
支持选择不同的数据包调度器。此为每个命名空间的sysctl参数。
默认值:"default"
stale_loss_cnt - INTEGER
用于判定子流失效(stale)的MPTCP层重传间隔次数阈值。当指定
子流在连续多个重传间隔内既无数据传输又有待处理数据时,将被标
记为失效状态。失效子流将被数据包调度器忽略。
设置较低的stale_loss_cnt值可实现快速主备切换,较高的值则能
最大化边缘场景(如高误码率链路或对端暂停数据处理等异常情况)
的链路利用率。
此为每个命名空间的sysctl参数。
默认值:4
syn_retrans_before_tcp_fallback - INTEGER
在回退到 TCP(即丢弃 MPTCP 选项)之前,SYN + MP_CAPABLE
报文的重传次数。换句话说,如果所有报文在传输过程中都被丢弃,
那么将会:
* 首次SYN携带MPTCP支持选项
* 按本参数值重传携带MPTCP选项的SYN包
* 后续重传将不再携带MPTCP支持选项
0 表示首次重传即丢弃MPTCP选项。
>=128 表示所有SYN重传均保留MPTCP选项设置过低的值可能增加
MPTCP黑洞误判几率。此为每个命名空间的sysctl参数。
默认值:2
@@ -0,0 +1,223 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/networking/msg_zerocopy.rst
:翻译:
王亚鑫 Wang Yaxin <wang.yaxin@zte.com.cn>
:校译:
- 徐鑫 xu xin <xu.xin16@zte.com.cn>
- 何配林 He Peilin <he.peilin@zte.com.cn>
============
MSG_ZEROCOPY
============
简介
====
MSG_ZEROCOPY 标志用于启用套接字发送调用的免拷贝功能。该功能目前适用于 TCP、UDP 和 VSOCK
(使用 virtio 传输)套接字。
机遇与注意事项
--------------
在用户进程与内核之间拷贝大型缓冲区可能会消耗大量资源。Linux 支持多种免拷贝的接口,如sendfile
和 splice。MSG_ZEROCOPY 标志将底层的拷贝避免机制扩展到了常见的套接字发送调用中。
免拷贝并非毫无代价。在实现上,它通过页面固定(page pinning)将按字节拷贝的成本替换为页面统计
(page accounting)和完成通知的开销。因此,MSG_ZEROCOPY 通常仅在写入量超过大约 10 KB 时
才有效。
页面固定还会改变系统调用的语义。它会暂时在进程和网络堆栈之间共享缓冲区。与拷贝不同,进程在系统
调用返回后不能立即覆盖缓冲区,否则可能会修改正在传输中的数据。内核的完整性不会受到影响,但有缺
陷的程序可能会破坏自己的数据流。
当内核返回数据可以安全修改的通知时,进程才可以修改数据。因此,将现有应用程序转换为使用
MSG_ZEROCOPY 并非总是像简单地传递该标志那样容易。
更多信息
--------
本文档的大部分内容是来自于 netdev 2.1 上发表的一篇长篇论文。如需更深入的信息,请参阅该论文和
演讲,或者浏览 LWN.net 上的精彩报道,也可以直接阅读源码。
论文、幻灯片、视频:
https://netdevconf.org/2.1/session.html?debruijn
LWN 文章:
https://lwn.net/Articles/726917/
补丁集:
[PATCH net-next v4 0/9] socket sendmsg MSG_ZEROCOPY
https://lore.kernel.org/netdev/20170803202945.70750-1-willemdebruijn.kernel@gmail.com
接口
====
传递 MSG_ZEROCOPY 标志是启用免拷贝功能的最明显步骤,但并非唯一的步骤。
套接字设置
----------
当应用程序向 send 系统调用传递未定义的标志时,内核通常会宽容对待。默认情况下,它会简单地忽略
这些标志。为了避免为那些偶然传递此标志的遗留进程启用免拷贝模式,进程必须首先通过设置套接字选项
来表明意图:
::
if (setsockopt(fd, SOL_SOCKET, SO_ZEROCOPY, &one, sizeof(one)))
error(1, errno, "setsockopt zerocopy");
传输
----
对 send(或 sendto、sendmsg、sendmmsg)本身的改动非常简单。只需传递新的标志即可。
::
ret = send(fd, buf, sizeof(buf), MSG_ZEROCOPY);
如果零拷贝操作失败,将返回 -1,并设置 errno 为 ENOBUFS。这种情况可能发生在套接字超出其
optmem 限制,或者用户超出其锁定页面的 ulimit 时。
混合使用免拷贝和拷贝
~~~~~~~~~~~~~~~~~~~~
许多工作负载同时包含大型和小型缓冲区。由于对于小数据包来说,免拷贝的成本高于拷贝,因此该
功能是通过标志实现的。带有标志的调用和没有标志的调用可以安全地混合使用。
通知
----
当内核认为可以安全地重用之前传递的缓冲区时,它必须通知进程。完成通知在套接字的错误队列上
排队,类似于传输时间戳接口。
通知本身是一个简单的标量值。每个套接字都维护一个内部的无符号 32 位计数器。每次带有
MSG_ZEROCOPY 标志的 send 调用成功发送数据时,计数器都会增加。如果调用失败或长度为零,
则计数器不会增加。该计数器统计系统调用的调用次数,而不是字节数。在 UINT_MAX 次调用后,
计数器会循环。
通知接收
~~~~~~~~
下面的代码片段展示了 API 的使用。在最简单的情况下,每次 send 系统调用后,都会对错误队列
进行轮询和 recvmsg 调用。
从错误队列读取始终是一个非阻塞操作。poll 调用用于阻塞,直到出现错误。它会在其输出标志中
设置 POLLERR。该标志不需要在 events 字段中设置。错误会无条件地发出信号。
::
pfd.fd = fd;
pfd.events = 0;
if (poll(&pfd, 1, -1) != 1 || pfd.revents & POLLERR == 0)
error(1, errno, "poll");
ret = recvmsg(fd, &msg, MSG_ERRQUEUE);
if (ret == -1)
error(1, errno, "recvmsg");
read_notification(msg);
这个示例仅用于演示目的。在实际应用中,不等待通知,而是每隔几次 send 调用就进行一次非阻塞
读取会更高效。
零拷贝通知可以与其他套接字操作乱序处理。通常,拥有错误队列套接字会阻塞其他操作,直到错误
被读取。然而,零拷贝通知具有零错误代码,因此不会阻塞 send 和 recv 调用。
通知批处理
~~~~~~~~~~~~
可以使用 recvmmsg 调用来一次性读取多个未决的数据包。这通常不是必需的。在每条消息中,内核
返回的不是一个单一的值,而是一个范围。当错误队列上有一个通知正在等待接收时,它会将连续的通
知合并起来。
当一个新的通知即将被排队时,它会检查队列尾部的通知的范围是否可以扩展以包含新的值。如果是这
样,它会丢弃新的通知数据包,并增大未处理通知的范围上限值。
对于按顺序确认数据的协议(如 TCP),每个通知都可以合并到前一个通知中,因此在任何时候在等待
的通知都不会超过一个。
有序交付是常见的情况,但不能保证。在重传和套接字拆除时,通知可能会乱序到达。
通知解析
~~~~~~~~
下面的代码片段演示了如何解析控制消息:前面代码片段中的 read_notification() 调用。通知
以标准错误格式 sock_extended_err 编码。
控制数据中的级别和类型字段是协议族特定的,对于 TCP 或 UDP 套接字,分别为 IP_RECVERR 或
IPV6_RECVERR。对于 VSOCK 套接字,cmsg_level 为 SOL_VSOCK,cmsg_type 为 VSOCK_RECVERR。
错误来源是新的类型 SO_EE_ORIGIN_ZEROCOPY。如前所述,ee_errno 为零,以避免在套接字上
阻塞地读取和写入系统调用。
32 位通知范围编码为 [ee_info, ee_data]。这个范围是包含边界值的。除了下面讨论的 ee_code
字段外,结构中的其他字段应被视为未定义的。
::
struct sock_extended_err *serr;
struct cmsghdr *cm;
cm = CMSG_FIRSTHDR(msg);
if (cm->cmsg_level != SOL_IP &&
cm->cmsg_type != IP_RECVERR)
error(1, 0, "cmsg");
serr = (void *) CMSG_DATA(cm);
if (serr->ee_errno != 0 ||
serr->ee_origin != SO_EE_ORIGIN_ZEROCOPY)
error(1, 0, "serr");
printf("completed: %u..%u\n", serr->ee_info, serr->ee_data);
延迟拷贝
~~~~~~~~
传递标志 MSG_ZEROCOPY 是向内核发出的一个提示,让内核采用免拷贝的策略,同时也是一种约
定,即内核会对完成通知进行排队处理。但这并不保证拷贝操作一定会被省略。
拷贝避免不总是适用的。不支持分散/聚集 I/O 的设备无法发送由内核生成的协议头加上零拷贝用户
数据组成的数据包。数据包可能需要在协议栈底层转换为一份私有数据副本,例如用于计算校验和。
在所有这些情况下,当内核释放对共享页面的持有权时,它会返回一个完成通知。该通知可能在(已
拷贝)数据完全传输之前到达。因此。零拷贝完成通知并不是传输完成通知。
如果数据不在缓存中,延迟拷贝可能会比立即在系统调用中拷贝开销更大。进程还会因通知处理而产
生成本,但却没有带来任何好处。因此,内核会在返回时通过在 ee_code 字段中设置标志
SO_EE_CODE_ZEROCOPY_COPIED 来指示数据是否以拷贝的方式完成。进程可以利用这个信号,在
同一套接字上后续的请求中停止传递 MSG_ZEROCOPY 标志。
实现
====
环回
----
对于 TCP 和 UDP:
如果接收进程不读取其套接字,发送到本地套接字的数据可能会无限期排队。无限期的通知延迟是不
可接受的。因此,所有使用 MSG_ZEROCOPY 生成并环回到本地套接字的数据包都将产生延迟拷贝。
这包括环回到数据包套接字(例如,tcpdump)和 tun 设备。
对于 VSOCK:
发送到本地套接字的数据路径与非本地套接字相同。
测试
====
更具体的示例代码可以在内核源码的 tools/testing/selftests/net/msg_zerocopy.c 中找到。
要留意环回约束问题。该测试可以在一对主机之间进行。但如果是在本地的一对进程之间运行,例如当使用
msg_zerocopy.sh 脚本在跨命名空间的虚拟以太网(veth)对之间运行时,测试将不会显示出任何性能
提升。为了便于测试,可以通过让 skb_orphan_frags_rx 与 skb_orphan_frags 相同,来暂时放宽
环回限制。
对于 VSOCK 类型套接字的示例可以在 tools/testing/vsock/vsock_test_zerocopy.c 中找到。
@@ -0,0 +1,362 @@
.. SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/networking/napi.rst
:翻译:
王亚鑫 Yaxin Wang <wang.yaxin@zte.com.cn>
====
NAPI
====
NAPI 是 Linux 网络堆栈中使用的事件处理机制。NAPI 的名称现在不再代表任何特定含义 [#]_。
在基本操作中,设备通过中断通知主机有新事件发生。主机随后调度 NAPI 实例来处理这些事件。
该设备也可以通过 NAPI 进行事件轮询,而无需先接收中断信号(:ref:`忙轮询<poll_zh_CN>`)。
NAPI 处理通常发生在软中断上下文中,但有一个选项,可以使用 :ref:`单独的内核线程<threaded_zh_CN>`
来进行 NAPI 处理。
总的来说,NAPI 为驱动程序抽象了事件(数据包接收和发送)处理的上下文环境和配置情况。
驱动程序API
===========
NAPI 最重要的两个元素是 struct napi_struct 和关联的 poll 方法。struct napi_struct
持有 NAPI 实例的状态,而方法则是与驱动程序相关的事件处理器。该方法通常会释放已传输的发送
(Tx)数据包并处理新接收的数据包。
.. _drv_ctrl_zh_CN:
控制API
-------
netif_napi_add() 和 netif_napi_del() 用于向系统中添加/删除一个 NAPI 实例。实例会被
附加到作为参数传递的 netdevice上(并在 netdevice 注销时自动删除)。实例在添加时处于禁
用状态。
napi_enable() 和 napi_disable() 管理禁用状态。禁用的 NAPI 不会被调度,并且保证其
poll 方法不会被调用。napi_disable() 会等待 NAPI 实例的所有权被释放。
这些控制 API 并非幂等的。控制 API 调用在面对数据路径 API 的并发使用时是安全的,但控制
API 调用顺序错误可能会导致系统崩溃、死锁或竞态条件。例如,连续多次调用 napi_disable()
会造成死锁。
数据路径API
-----------
napi_schedule() 是调度 NAPI 轮询的基本方法。驱动程序应在其中断处理程序中调用此函数
(更多信息请参见 :ref:`drv_sched_zh_CN`)。成功的 napi_schedule() 调用将获得 NAPI 实例
的所有权。
之后,在 NAPI 被调度后,驱动程序的 poll 方法将被调用以处理事件/数据包。该方法接受一个
``budget`` 参数 - 驱动程序可以处理任意数量的发送 (Tx) 数据包完成,但处理最多处理
``budget`` 个接收 (Rx) 数据包。处理接收数据包通常开销更大。
换句话说,对于接收数据包的处理,``budget`` 参数限制了驱动程序在单次轮询中能够处理的数
据包数量。当 ``budget`` 为 0 时,像页面池或 XDP 这类专门用于接收的 API 根本无法使用。
无论 ``budget`` 的值是多少,skb 的发送处理都应该进行,但是如果 ``budget`` 参数为 0,
驱动程序就不能调用任何 XDP(或页面池)API。
.. warning::
如果内核仅尝试处理skb的发送完成情况,而不处理接收 (Rx) 或 XDP 数据包,那么 ``budget``
参数可能为 0。
轮询方法会返回已完成的工作量。如果驱动程序仍有未完成的工作(例如,``budget`` 已用完),
轮询方法应精确返回 ``budget`` 的值。在这种情况下,NAPI 实例将再次被处理 / 轮询(无需
重新调度)。
如果事件处理已完成(所有未处理的数据包都已处理完毕),轮询方法在返回之前应调用 napi_complete_done()。
napi_complete_done() 会释放实例的所有权。
.. warning::
当出现既完成了所有事件处理,又恰好达到了 ``budget`` 数量的情况时,必须谨慎处理。因为没
有办法将这种(很少出现的)情况报告给协议栈,所以驱动程序要么不调用 napi_complete_done()
并等待再次被调用,要么返回 ``budget - 1``。
当 ``budget`` 为 0 时,napi_complete_done() 绝对不能被调用。
调用序列
--------
驱动程序不应假定调用的顺序是固定不变的。即使驱动程序没有调度该实例,轮询方法也可能会被调用
(除非该实例处于禁用状态)。同样,即便 napi_schedule() 调用成功,也不能保证轮询方法一定
会被调用(例如,如果该实例被禁用)。
正如在 :ref:`drv_ctrl_zh_CN` 部分所提到的,napi_disable() 以及后续对轮询方法的调用,
仅会等待该实例的所有权被释放,而不会等待轮询方法退出。这意味着,驱动程序在调用 napi_complete_done()
之后,应避免访问任何数据结构。
.. _drv_sched_zh_CN:
调度与IRQ屏蔽
-------------
驱动程序应在调度 NAPI 实例后保持中断屏蔽 - 直到 NAPI 轮询完成,任何进一步的中断都是不必要的。
显式屏蔽中断的驱动程序(而非设备自动屏蔽 IRQ)应使用 napi_schedule_prep() 和
__napi_schedule() 调用:
.. code-block:: c
if (napi_schedule_prep(&v->napi)) {
mydrv_mask_rxtx_irq(v->idx);
/* 在屏蔽后调度以避免竞争 */
__napi_schedule(&v->napi);
}
IRQ 仅应在成功调用 napi_complete_done() 后取消屏蔽:
.. code-block:: c
if (budget && napi_complete_done(&v->napi, work_done)) {
mydrv_unmask_rxtx_irq(v->idx);
return min(work_done, budget - 1);
}
napi_schedule_irqoff() 是 napi_schedule() 的一个变体,它利用了在中断请求(IRQ)上下文
环境中调用所带来的特性(无需屏蔽中断)。如果中断请求(IRQ)是通过线程处理的(例如启用了
``PREEMPT_RT`` 时的情况),napi_schedule_irqoff() 会回退为使用 napi_schedule() 。
实例到队列的映射
----------------
现代设备每个接口有多个 NAPI 实例(struct napi_struct)。关于实例如何映射到队列和中断没有
严格要求。NAPI 主要是事件处理/轮询抽象,没有用户可见的语义。也就是说,大多数网络设备最终以
非常相似的方式使用 NAPI。
NAPI 实例最常以 1:1:1 映射到中断和队列对(队列对是由一个接收队列和一个发送队列组成的一组
队列)。
在不太常见的情况下,一个 NAPI 实例可能会用于处理多个队列,或者在单个内核上,接收(Rx)队列
和发送(Tx)队列可以由不同的 NAPI 实例来处理。不过,无论队列如何分配,通常 NAPI 实例和中断
之间仍然保持一一对应的关系。
值得注意的是,ethtool API 使用了 “通道” 这一术语,每个通道可以是 ``rx`` (接收)、``tx``
(发送)或 ``combined`` (组合)类型。目前尚不清楚一个通道具体由什么构成,建议的理解方式是
将一个通道视为一个为特定类型队列提供服务的 IRQ(中断请求)/ NAPI 实例。例如,配置为 1 个
``rx`` 通道、1 个 ``tx`` 通道和 1 个 ``combined`` 通道的情况下,预计会使用 3 个中断、
2 个接收队列和 2 个发送队列。
持久化NAPI配置
--------------
驱动程序常常会动态地分配和释放 NAPI 实例。这就导致每当 NAPI 实例被重新分配时,与 NAPI 相关
的用户配置就会丢失。netif_napi_add_config() API接口通过将每个 NAPI 实例与基于驱动程序定义
的索引值(如队列编号)的持久化 NAPI 配置相关联,从而避免了这种配置丢失的情况。
使用此 API 可实现持久化的 NAPI 标识符(以及其他设置),这对于使用 ``SO_INCOMING_NAPI_ID``
的用户空间程序来说是有益的。有关其他 NAPI 配置的设置,请参阅以下章节。
驱动程序应尽可能尝试使用 netif_napi_add_config()。
用户API
=======
用户与 NAPI 的交互依赖于 NAPI 实例 ID。这些实例 ID 仅通过 ``SO_INCOMING_NAPI_ID`` 套接字
选项对用户可见。
用户可以使用 Netlink 来查询某个设备或设备队列的 NAPI 标识符。这既可以在用户应用程序中通过编程
方式实现,也可以使用内核源代码树中包含的一个脚本:tools/net/ynl/pyynl/cli.py 来完成。
例如,使用该脚本转储某个设备的所有队列(这将显示每个队列的 NAPI 标识符):
.. code-block:: bash
$ kernel-source/tools/net/ynl/pyynl/cli.py \
--spec Documentation/netlink/specs/netdev.yaml \
--dump queue-get \
--json='{"ifindex": 2}'
有关可用操作和属性的更多详细信息,请参阅 ``Documentation/netlink/specs/netdev.yaml``。
软件IRQ合并
-----------
默认情况下,NAPI 不执行任何显式的事件合并。在大多数场景中,数据包的批量处理得益于设备进行
的中断请求(IRQ)合并。不过,在某些情况下,软件层面的合并操作也很有帮助。
可以将 NAPI 配置为设置一个重新轮询定时器,而不是在处理完所有数据包后立即取消屏蔽硬件中断。
网络设备的 ``gro_flush_timeout`` sysfs 配置项可用于控制该定时器的延迟时间,而 ``napi_defer_hard_irqs``
则用于控制在 NAPI 放弃并重新启用硬件中断之前,连续进行空轮询的次数。
上述参数也可以通过 Netlink 的 netdev-genl 接口,基于每个 NAPI 实例进行设置。当通过
Netlink 进行配置且是基于每个 NAPI 实例设置时,上述参数使用连字符(-)而非下划线(_)
来命名,即 ``gro-flush-timeout`` 和 ``napi-defer-hard-irqs``。
基于每个 NAPI 实例的配置既可以在用户应用程序中通过编程方式完成,也可以使用内核源代码树中的
一个脚本实现,该脚本为 ``tools/net/ynl/pyynl/cli.py``。
例如,通过如下方式使用该脚本:
.. code-block:: bash
$ kernel-source/tools/net/ynl/pyynl/cli.py \
--spec Documentation/netlink/specs/netdev.yaml \
--do napi-set \
--json='{"id": 345,
"defer-hard-irqs": 111,
"gro-flush-timeout": 11111}'
类似地,参数 ``irq-suspend-timeout`` 也可以通过 netlink 的 netdev-genl 设置。没有全局
的 sysfs 参数可用于设置这个值。
``irq-suspend-timeout`` 用于确定应用程序可以完全挂起 IRQ 的时长。与 SO_PREFER_BUSY_POLL
结合使用,后者可以通过 ``EPIOCSPARAMS`` ioctl 在每个 epoll 上下文中设置。
.. _poll_zh_CN:
忙轮询
------
忙轮询允许用户进程在设备中断触发前检查传入的数据包。与其他忙轮询一样,它以 CPU 周期换取更低
的延迟(生产环境中 NAPI 忙轮询的使用尚不明确)。
通过在选定套接字上设置 ``SO_BUSY_POLL`` 或使用全局 ``net.core.busy_poll`` 和 ``net.core.busy_read``
等 sysctls 启用忙轮询。还存在基于 io_uring 的 NAPI 忙轮询 API 可使用。
基于epoll的忙轮询
-----------------
可以从 ``epoll_wait`` 调用直接触发数据包处理。为了使用此功能,用户应用程序必须确保添加到
epoll 上下文的所有文件描述符具有相同的 NAPI ID。
如果应用程序使用专用的 acceptor 线程,那么该应用程序可以获取传入连接的 NAPI ID(使用
SO_INCOMING_NAPI_ID)然后将该文件描述符分发给工作线程。工作线程将该文件描述符添加到其
epoll 上下文。这确保了每个工作线程的 epoll 上下文中所包含的文件描述符具有相同的 NAPI ID。
或者,如果应用程序使用 SO_REUSEPORT,可以插入 bpf 或 ebpf 程序来分发传入连接,使得每个
线程只接收具有相同 NAPI ID 的连接。但是必须谨慎处理系统中可能存在多个网卡的情况。
为了启用忙轮询,有两种选择:
1. ``/proc/sys/net/core/busy_poll`` 可以设置为微秒数以在忙循环中等待事件。这是一个系统
范围的设置,将导致所有基于 epoll 的应用程序在调用 epoll_wait 时忙轮询。这可能不是理想
的情况,因为许多应用程序可能不需要忙轮询。
2. 使用最新内核的应用程序可以在 epoll 上下文的文件描述符上发出 ioctl 来设置(``EPIOCSPARAMS``)
或获取(``EPIOCGPARAMS``) ``struct epoll_params``,用户程序定义如下:
.. code-block:: c
struct epoll_params {
uint32_t busy_poll_usecs;
uint16_t busy_poll_budget;
uint8_t prefer_busy_poll;
/* 将结构填充到 64 位的倍数 */
uint8_t __pad;
};
IRQ缓解
-------
虽然忙轮询旨在用于低延迟应用,但类似的机制可用于减少中断请求。
每秒高请求的应用程序(尤其是路由/转发应用程序和特别使用 AF_XDP 套接字的应用程序)
可能希望在处理完一个请求或一批数据包之前不被中断。
此类应用程序可以向内核承诺会定期执行忙轮询操作,而驱动程序应将设备的中断请求永久屏蔽。
通过使用 ``SO_PREFER_BUSY_POLL`` 套接字选项可启用此模式。为避免系统出现异常,如果
在 ``gro_flush_timeout`` 时间内没有进行任何忙轮询调用,该承诺将被撤销。对于基于
epoll 的忙轮询应用程序,可以将 ``struct epoll_params`` 结构体中的 ``prefer_busy_poll``
字段设置为 1,并使用 ``EPIOCSPARAMS`` 输入 / 输出控制(ioctl)操作来启用此模式。
更多详情请参阅上述章节。
NAPI 忙轮询的 budget 低于默认值(这符合正常忙轮询的低延迟意图)。减少中断请求的场景中
并非如此,因此 budget 可以通过 ``SO_BUSY_POLL_BUDGET`` 套接字选项进行调整。对于基于
epoll 的忙轮询应用程序,可以通过调整 ``struct epoll_params`` 中的 ``busy_poll_budget``
字段为特定值,并使用 ``EPIOCSPARAMS`` ioctl 在特定 epoll 上下文中设置。更多详细信
息请参见上述部分。
需要注意的是,为 ``gro_flush_timeout`` 选择较大的值会延迟中断请求,以实现更好的批
量处理,但在系统未满载时会增加延迟。为 ``gro_flush_timeout`` 选择较小的值可能会因
设备中断请求和软中断处理而干扰尝试进行忙轮询的用户应用程序。应权衡这些因素后谨慎选择
该值。基于 epoll 的忙轮询应用程序可以通过为 ``maxevents`` 选择合适的值来减少用户
处理的干扰。
用户可能需要考虑使用另一种方法,IRQ 挂起,以帮助应对这些权衡问题。
IRQ挂起
-------
IRQ 挂起是一种机制,其中设备 IRQ 在 epoll 触发 NAPI 数据包处理期间被屏蔽。
只要应用程序对 epoll_wait 的调用成功获取事件,内核就会推迟 IRQ 挂起定时器。如果
在忙轮询期间没有获取任何事件(例如,因为网络流量减少),则会禁用IRQ挂起功能,并启
用上述减少中断请求的策略。
这允许用户在 CPU 消耗和网络处理效率之间取得平衡。
要使用此机制:
1. 每个 NAPI 的配置参数 ``irq-suspend-timeout`` 应设置为应用程序可以挂起
IRQ 的最大时间(纳秒)。这通过 netlink 完成,如上所述。此超时时间作为一
种安全机制,如果应用程序停滞,将重新启动中断驱动程序的中断处理。此值应选择
为覆盖用户应用程序调用 epoll_wait 处理数据所需的时间,需注意的是,应用程
序可通过在调用 epoll_wait 时设置 ``max_events`` 来控制获取的数据量。
2. sysfs 参数或每个 NAPI 的配置参数 ``gro_flush_timeout`` 和 ``napi_defer_hard_irqs``
可以设置为较低值。它们将用于在忙轮询未找到数据时延迟 IRQs。
3. 必须将 ``prefer_busy_poll`` 标志设置为 true。如前文所述,可使用 ``EPIOCSPARAMS``
ioctl操作来完成此设置。
4. 应用程序按照上述方式使用 epoll 触发 NAPI 数据包处理。
如上所述,只要后续对 epoll_wait 的调用向用户空间返回事件,``irq-suspend-timeout``
就会被推迟并且 IRQ 会被禁用。这允许应用程序在无干扰的情况下处理数据。
一旦 epoll_wait 的调用没有找到任何事件,IRQ 挂起会被自动禁用,并且 ``gro_flush_timeout``
和 ``napi_defer_hard_irqs`` 缓解机制将开始起作用。
预期是 ``irq-suspend-timeout`` 的设置值会远大于 ``gro_flush_timeout``,因为 ``irq-suspend-timeout``
应在一个用户空间处理周期内暂停中断请求。
虽然严格来说不必通过 ``napi_defer_hard_irqs`` 和 ``gro_flush_timeout`` 来执行 IRQ 挂起,
但强烈建议这样做。
中断请求挂起会使系统在轮询模式和由中断驱动的数据包传输模式之间切换。在网络繁忙期间,``irq-suspend-timeout``
会覆盖 ``gro_flush_timeout``,使系统保持忙轮询状态,但是当 epoll 未发现任何事件时,``gro_flush_timeout``
和 ``napi_defer_hard_irqs`` 的设置将决定下一步的操作。
有三种可能的网络处理和数据包交付循环:
1) 硬中断 -> 软中断 -> NAPI 轮询;基本中断交付
2) 定时器 -> 软中断 -> NAPI 轮询;延迟的 IRQ 处理
3) epoll -> 忙轮询 -> NAPI 轮询;忙循环
循环 2 可以接管循环 1,如果设置了 ``gro_flush_timeout`` 和 ``napi_defer_hard_irqs``。
如果设置了 ``gro_flush_timeout`` 和 ``napi_defer_hard_irqs``,循环 2 和 3 将互相“争夺”控制权。
在繁忙时期,``irq-suspend-timeout`` 用作循环 2 的定时器,这基本上使网络处理倾向于循环 3。
如果不设置 ``gro_flush_timeout`` 和 ``napi_defer_hard_irqs``,循环 3 无法从循环 1 接管。
因此,建议设置 ``gro_flush_timeout`` 和 ``napi_defer_hard_irqs``,因为若不这样做,设置
``irq-suspend-timeout`` 可能不会有明显效果。
.. _threaded_zh_CN:
线程化NAPI
----------
线程化 NAPI 是一种操作模式,它使用专用的内核线程而非软件中断上下文来进行 NAPI 处理。这种配置
是针对每个网络设备的,并且会影响该设备的所有 NAPI 实例。每个 NAPI 实例将生成一个单独的线程
(称为 ``napi/${ifc-name}-${napi-id}`` )。
建议将每个内核线程固定到单个 CPU 上,这个 CPU 与处理中断的 CPU 相同。请注意,中断请求(IRQ)
和 NAPI 实例之间的映射关系可能并不简单(并且取决于驱动程序)。NAPI 实例 ID 的分配顺序将与内
核线程的进程 ID 顺序相反。
线程化 NAPI 是通过向网络设备的 sysfs 目录中的 ``threaded`` 文件写入 0 或 1 来控制的。
.. rubric:: 脚注
.. [#] NAPI 最初在 2.4 Linux 中被称为 New API。
@@ -0,0 +1,92 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/networking/netif-msg.rst
:翻译:
王亚鑫 Wang Yaxin <wang.yaxin@zte.com.cn>
================
网络接口消息级别
================
网络接口消息级别设置的设计方案。
历史背景
--------
调试消息接口的设计遵循并受制于向后兼容性及历史实践。理解其发展历史有助于把握
当前实践,并将其与旧版驱动代码相关联。
自Linux诞生之初,每个网络设备驱动均包含一个本地整型变量以控制调试消息级别。
消息级别范围为0至7,数值越大表示输出越详细。
消息级别的定义在3级之后未明确细化,但实际实现通常与指定级别相差±1。驱动程序
成熟后,冗余的详细级别消息常被移除。
- 0 最简消息,仅显示致命错误的关键信息。
- 1 标准消息,初始化状态。无运行时消息。
- 2 特殊介质选择消息,通常由定时器驱动。
- 3 接口开启和停止消息,包括正常状态信息。
- 4 Tx/Rx帧错误消息及异常驱动操作。
- 5 Tx数据包队列信息、中断事件。
- 6 每个完成的Tx数据包和接收的Rx数据包状态。
- 7 Tx/Rx数据包初始内容。
最初,该消息级别变量在各驱动中具有唯一名称(如"lance_debug"),便于通过
内核符号调试器定位和修改其设置。模块化内核出现后,变量统一重命名为"debug",
并作为模块参数设置。
这种方法效果良好。然而,人们始终对附加功能存在需求。多年来,以下功能逐渐
成为合理且易于实现的增强方案:
- 通过ioctl()调用修改消息级别。
- 按接口而非驱动设置消息级别。
- 对发出的消息类型进行更具选择性的控制。
netif_msg 建议添加了这些功能,仅带来了轻微的复杂性增加和代码规模增长。
推荐方案如下:
- 保留驱动级整型变量"debug"作为模块参数,默认值为'1'。
- 添加一个名为 "msg_enable" 的接口私有变量。该变量是位图而非级别,
并按如下方式初始化::
1 << debug
或更精确地说::
debug < 0 ? 0 : 1 << min(sizeof(int)-1, debug)
消息应从以下形式更改::
if (debug > 1)
printk(MSG_DEBUG "%s: ...
改为::
if (np->msg_enable & NETIF_MSG_LINK)
printk(MSG_DEBUG "%s: ...
消息级别命名对应关系
========= =================== ============
旧级别 名称 位位置
========= =================== ============
1 NETIF_MSG_PROBE 0x0002
2 NETIF_MSG_LINK 0x0004
2 NETIF_MSG_TIMER 0x0004
3 NETIF_MSG_IFDOWN 0x0008
3 NETIF_MSG_IFUP 0x0008
4 NETIF_MSG_RX_ERR 0x0010
4 NETIF_MSG_TX_ERR 0x0010
5 NETIF_MSG_TX_QUEUED 0x0020
5 NETIF_MSG_INTR 0x0020
6 NETIF_MSG_TX_DONE 0x0040
6 NETIF_MSG_RX_STATUS 0x0040
7 NETIF_MSG_PKTDATA 0x0080
========= =================== ============
@@ -0,0 +1,97 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/networking/netmem.rst
:翻译:
王亚鑫 Wang Yaxin <wang.yaxin@zte.com.cn>
==================
网络驱动支持Netmem
==================
本文档概述了网络驱动支持netmem(一种抽象内存类型)的要求,该内存类型
支持设备内存 TCP 等功能。通过支持netmem,驱动可以灵活适配不同底层内
存类型(如设备内存TCP),且无需或仅需少量修改。
Netmem的优势:
* 灵活性:netmem 可由不同内存类型(如 struct page、DMA-buf)支持,
使驱动程序能够支持设备内存 TCP 等各种用例。
* 前瞻性:支持netmem的驱动可无缝适配未来依赖此功能的新特性。
* 简化开发:驱动通过统一API与netmem交互,无需关注底层内存的实现差异。
驱动RX要求
==========
1. 驱动必须支持page_pool。
2. 驱动必须支持tcp-data-split ethtool选项。
3. 驱动必须使用page_pool netmem API处理有效载荷内存。当前netmem API
与page API一一对应。转换时需要将page API替换为netmem API,并用驱动
中的netmem_refs跟踪内存而非 `struct page *`:
- page_pool_alloc -> page_pool_alloc_netmem
- page_pool_get_dma_addr -> page_pool_get_dma_addr_netmem
- page_pool_put_page -> page_pool_put_netmem
目前并非所有页 pageAPI 都有对应的 netmem 等效接口。如果你的驱动程序
依赖某个尚未实现的 netmem API,请直接实现并提交至 netdev@邮件列表,
或联系维护者及 almasrymina@google.com 协助添加该 netmem API。
4. 驱动必须设置以下PP_FLAGS:
- PP_FLAG_DMA_MAP:驱动程序无法对 netmem 执行 DMA 映射。此时驱动
程序必须将 DMA 映射操作委托给 page_pool,由其判断何时适合(或不适合)
进行 DMA 映射。
- PP_FLAG_DMA_SYNC_DEV:驱动程序无法保证 netmem 的 DMA 地址一定能
完成 DMA 同步。此时驱动程序必须将 DMA 同步操作委托给 page_pool,由
其判断何时适合(或不适合)进行 DMA 同步。
- PP_FLAG_ALLOW_UNREADABLE_NETMEM:仅当启用 tcp-data-split 时,
驱动程序必须显式设置此标志。
5. 驱动不得假设netmem可读或基于页。当netmem_address()返回NULL时,表示
内存不可读。驱动需正确处理不可读的netmem,例如,当netmem_address()返回
NULL时,避免访问内容。
理想情况下,驱动程序不应通过netmem_is_net_iov()等辅助函数检查底层
netmem 类型,也不应通过netmem_to_page()或netmem_to_net_iov()将
netmem 转换为其底层类型。在大多数情况下,系统会提供抽象这些复杂性的
netmem 或 page_pool 辅助函数(并可根据需要添加更多)。
6. 驱动程序必须使用page_pool_dma_sync_netmem_for_cpu()代替dma_sync_single_range_for_cpu()。
对于某些内存提供者,CPU 的 DMA 同步将由 page_pool 完成;而对于其他提供者
(特别是 dmabuf 内存提供者),CPU 的 DMA 同步由使用 dmabuf API 的用户空
间负责。驱动程序必须将整个 DMA 同步操作委托给 page_pool,以确保操作正确执行。
7. 避免在 page_pool 之上实现特定于驱动程序内存回收机制。由于 netmem 可能
不由struct page支持,驱动程序不能保留struct page来进行自定义回收。不过,
可为此目的通过page_pool_fragment_netmem()或page_pool_ref_netmem()保留
page_pool 引用,但需注意某些 netmem 类型的循环时间可能更长(例如零拷贝场景
下用户空间持有引用的情况)。
驱动TX要求
==========
1. 驱动程序绝对不能直接把 netmem 的 dma_addr 传递给任何 dma-mapping API。这
是由于 netmem 的 dma_addr 可能源自 dma-buf 这类和 dma-mapping API 不兼容的
源头。
应当使用netmem_dma_unmap_page_attrs()和netmem_dma_unmap_addr_set()等辅助
函数来替代dma_unmap_page[_attrs]()、dma_unmap_addr_set()。不管 dma_addr
来源如何,netmem 的这些变体都能正确处理 netmem dma_addr,在合适的时候会委托给
dma-mapping API 去处理。
目前,并非所有的 dma-mapping API 都有对应的 netmem 版本。要是你的驱动程序需要
使用某个还不存在的 netmem API,你可以自行添加并提交到 netdev@,也可以联系维护
人员或者发送邮件至 almasrymina@google.com 寻求帮助。
2. 驱动程序应将 `netdev->netmem_tx` 设置为适当的模式:
- `NETMEM_TX_DMA`:适用于执行 DMA 的物理设备。
- `NETMEM_TX_NO_DMA`:适用于不执行 DMA 的虚拟或透传设备,但仍支持
处理 netmem 支持的 skb。
@@ -0,0 +1,674 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/networking/timestamping.rst
:翻译:
王亚鑫 Wang Yaxin <wang.yaxin@zte.com.cn>
======
时间戳
======
1. 控制接口
===========
接收网络数据包时间戳的接口包括:
SO_TIMESTAMP
为每个传入数据包生成(不一定是单调的)系统时间时间戳。通过 recvmsg()
在控制消息中以微秒分辨率报告时间戳。
SO_TIMESTAMP 根据架构类型和 libc 的 lib 中的 time_t 表示方式定义为
SO_TIMESTAMP_NEW 或 SO_TIMESTAMP_OLD。
SO_TIMESTAMP_OLD 和 SO_TIMESTAMP_NEW 的控制消息格式分别为
struct __kernel_old_timeval 和 struct __kernel_sock_timeval。
SO_TIMESTAMPNS
与 SO_TIMESTAMP 相同的时间戳机制,但以 struct timespec 格式报告时间戳,
纳秒分辨率。
SO_TIMESTAMPNS 根据架构类型和 libc 的 time_t 表示方式定义为
SO_TIMESTAMPNS_NEW 或 SO_TIMESTAMPNS_OLD。
控制消息格式对于 SO_TIMESTAMPNS_OLD 为 struct timespec,
对于 SO_TIMESTAMPNS_NEW 为 struct __kernel_timespec。
IP_MULTICAST_LOOP + SO_TIMESTAMP[NS]
仅用于多播:通过读取回环数据包接收时间戳,获得近似的传输时间戳。
SO_TIMESTAMPING
在接收、传输或两者时生成时间戳。支持多个时间戳源,包括硬件。
支持为流套接字生成时间戳。
1.1 SO_TIMESTAMP(也包括 SO_TIMESTAMP_OLD 和 SO_TIMESTAMP_NEW)
---------------------------------------------------------------
此套接字选项在接收路径上启用数据报的时间戳。由于目标套接字(如果有)
在网络栈早期未知,因此必须为所有数据包启用此功能。所有早期接收的时间
戳选项也是如此。
有关接口详细信息,请参阅 `man 7 socket`。
始终使用 SO_TIMESTAMP_NEW 时间戳以获得 struct __kernel_sock_timeval
格式的时间戳。
如果时间在 2038 年后,SO_TIMESTAMP_OLD 在 32 位机器上将返回错误的时间戳。
1.2 SO_TIMESTAMPNS(也包括 SO_TIMESTAMPNS_OLD 和 SO_TIMESTAMPNS_NEW)
---------------------------------------------------------------------
此选项与 SO_TIMESTAMP 相同,但返回数据类型有所不同。其 struct timespec
能达到比 SO_TIMESTAMP 的 timeval(毫秒)更高的分辨率(纳秒)时间戳。
始终使用 SO_TIMESTAMPNS_NEW 时间戳获得 struct __kernel_timespec 格式
的时间戳。
如果时间在 2038 年后,SO_TIMESTAMPNS_OLD 在 32 位机器上将返回错误的时间戳。
1.3 SO_TIMESTAMPING(也包括 SO_TIMESTAMPING_OLD 和 SO_TIMESTAMPING_NEW)
------------------------------------------------------------------------
支持多种类型的时间戳请求。因此,此套接字选项接受标志位图,而不是布尔值。在::
err = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val));
val 是一个整数,设置了以下任何位。设置其他位将返回 EINVAL 且不更改当前状态。
这个套接字选项配置以下几个方面的时间戳生成:
为单个 sk_buff 结构体生成时间戳(1.3.1);
将时间戳报告到套接字的错误队列(1.3.2);
配置相关选项(1.3.3);
也可以通过 cmsg 为单个 sendmsg 调用启用时间戳生成(1.3.4)。
1.3.1 时间戳生成
^^^^^^^^^^^^^^^^
某些位是向协议栈请求尝试生成时间戳。它们的任何组合都是有效的。对这些位的更改适
用于新创建的数据包,而不是已经在协议栈中的数据包。因此,可以通过在两个 setsockopt
调用之间嵌入 send() 调用来选择性地为数据包子集请求时间戳(例如,用于采样),
一个用于启用时间戳生成,一个用于禁用它。时间戳也可能由于特定套接字请求之外的原
因而生成,例如在当系统范围内启用接收时间戳时,如前所述。
SOF_TIMESTAMPING_RX_HARDWARE:
请求由网络适配器生成的接收时间戳。
SOF_TIMESTAMPING_RX_SOFTWARE:
当数据进入内核时请求接收时间戳。这些时间戳在设备驱动程序将数据包交给内核接收
协议栈后生成。
SOF_TIMESTAMPING_TX_HARDWARE:
请求由网络适配器生成的传输时间戳。此标志可以通过套接字选项和控制消息启用。
SOF_TIMESTAMPING_TX_SOFTWARE:
当数据离开内核时请求传输(TX)时间戳。这些时间戳由设备驱动程序生成,并且尽
可能贴近网络接口发送点,但始终在内核将数据包传递给网络接口之前生成。因此,
它们需要驱动程序支持,且可能并非所有设备都可用。此标志可通过套接字选项和
控制消息两种方式启用。
SOF_TIMESTAMPING_TX_SCHED:
在进入数据包调度器之前请求传输时间戳。内核传输延迟(如果很长)通常由排队
延迟主导。此时间戳与在 SOF_TIMESTAMPING_TX_SOFTWARE 处获取的时间戳之
间的差异将暴露此延迟,并且与协议处理无关。协议处理中产生的延迟(如果有)
可以通过从 send() 之前立即获取的用户空间时间戳中减去此时间戳来计算。在
具有虚拟设备的机器上,传输的数据包通过多个设备和多个数据包调度器,在每层
生成时间戳。这允许对排队延迟进行细粒度测量。此标志可以通过套接字选项和控
制消息启用。
SOF_TIMESTAMPING_TX_ACK:
请求在发送缓冲区中的所有数据都已得到确认时生成传输(TX)时间戳。此选项
仅适用于可靠协议,目前仅在TCP协议中实现。对于该协议,它可能会过度报告
测量结果,因为时间戳是在send()调用时缓冲区中的所有数据(包括该缓冲区)
都被确认时生成的,即累积确认。该机制会忽略选择确认(SACK)和前向确认
(FACK)。此标志可通过套接字选项和控制消息两种方式启用。
SOF_TIMESTAMPING_TX_COMPLETION:
在数据包传输完成时请求传输时间戳。完成时间戳由内核在从硬件接收数据包完成
报告时生成。硬件可能一次报告多个数据包,完成时间戳反映报告的时序而不是实
际传输时间。此标志可以通过套接字选项和控制消息启用。
1.3.2 时间戳报告
^^^^^^^^^^^^^^^^
其他三个位控制将在生成的控制消息中报告哪些时间戳。对这些位的更改在协议栈中
的时间戳报告位置立即生效。仅当数据包设置了相关的时间戳生成请求时,才会报告
其时间戳。
SOF_TIMESTAMPING_SOFTWARE:
在可用时报告任何软件时间戳。
SOF_TIMESTAMPING_SYS_HARDWARE:
此选项已被弃用和忽略。
SOF_TIMESTAMPING_RAW_HARDWARE:
在可用时报告由 SOF_TIMESTAMPING_TX_HARDWARE 或 SOF_TIMESTAMPING_RX_HARDWARE
生成的硬件时间戳。
1.3.3 时间戳选项
^^^^^^^^^^^^^^^^
接口支持以下选项
SOF_TIMESTAMPING_OPT_ID:
每个数据包生成一个唯一标识符。一个进程可以同时存在多个未完成的时间戳请求。
数据包在传输路径中可能会发生重排序(例如在数据包调度器中)。在这种情况下,
时间戳会以与原始send()调用不同的顺序排队到错误队列中。如此一来,仅根据
时间戳顺序或 payload(有效载荷)检查,并不总能将时间戳与原始send()调用
唯一匹配。
此选项在 send() 时将每个数据包与唯一标识符关联,并与时间戳一起返回。
标识符源自每个套接字的 u32 计数器(会回绕)。对于数据报套接字,计数器
随每个发送的数据包递增。对于流套接字,它随每个字节递增。对于流套接字,
还要设置 SOF_TIMESTAMPING_OPT_ID_TCP,请参阅下面的部分。
计数器从零开始。在首次启用套接字选项时初始化。在禁用后再重新启用选项时
重置。重置计数器不会更改系统中现有数据包的标识符。
此选项仅针对传输时间戳实现。在这种情况下,时间戳总是与sock_extended_err
结构体一起回环。该选项会修改ee_data字段,以传递一个在该套接字所有同时
存在的未完成时间戳请求中唯一的 ID。
进程可以通过控制消息SCM_TS_OPT_ID(TCP 套接字不支持)传递特定 ID,
从而选择性地覆盖默认生成的 ID,示例如下::
struct msghdr *msg;
...
cmsg = CMSG_FIRSTHDR(msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_TS_OPT_ID;
cmsg->cmsg_len = CMSG_LEN(sizeof(__u32));
*((__u32 *) CMSG_DATA(cmsg)) = opt_id;
err = sendmsg(fd, msg, 0);
SOF_TIMESTAMPING_OPT_ID_TCP:
与 SOF_TIMESTAMPING_OPT_ID 一起传递给新的 TCP 时间戳应用程序。
SOF_TIMESTAMPING_OPT_ID 定义了流套接字计数器的增量,但其起始点
并不完全显而易见。此选项修复了这一点。
对于流套接字,如果设置了 SOF_TIMESTAMPING_OPT_ID,则此选项应始终
设置。在数据报套接字上,选项没有效果。
一个合理的期望是系统调用后计数器重置为零,因此后续写入 N 字节将生成
计数器为 N-1 的时间戳。SOF_TIMESTAMPING_OPT_ID_TCP 在所有条件下
都实现了此行为。
SOF_TIMESTAMPING_OPT_ID 不带修饰符时通常报告相同,特别是在套接字选项
在无数据传输时设置时。如果正在传输数据,它可能与输出队列的长度(SIOCOUTQ)
偏差。
差异是由于基于 snd_una 与 write_seq 的。snd_una 是 peer 确认的 stream
的偏移量。这取决于外部因素,例如网络 RTT。write_seq 是进程写入的最后一个
字节。此偏移量不受外部输入影响。
差异细微,在套接字选项初始化时配置时不易察觉,但 SOF_TIMESTAMPING_OPT_ID_TCP
行为在任何时候都更稳健。
SOF_TIMESTAMPING_OPT_CMSG:
支持所有时间戳数据包的 recv() cmsg。控制消息已无条件地在所有接收时间戳数据包
和 IPv6 数据包上支持,以及在发送时间戳数据包的 IPv4 数据包上支持。此选项扩展
了它们以在发送时间戳数据包的 IPv4 数据包上支持。一个用例是启用 socket 选项
IP_PKTINFO 以关联数据包与其出口设备,通过启用 socket 选项 IP_PKTINFO 同时。
SOF_TIMESTAMPING_OPT_TSONLY:
仅适用于传输时间戳。使内核返回一个 cmsg 与一个空数据包一起,而不是与原
始数据包一起。这减少了套接字接收预算(SO_RCVBUF)中收取的内存量,并即使
在 sysctl net.core.tstamp_allow_data 为 0 时也提供时间戳。此选项禁用
SOF_TIMESTAMPING_OPT_CMSG。
SOF_TIMESTAMPING_OPT_STATS:
与传输时间戳一起获取的选项性统计信息。它必须与 SOF_TIMESTAMPING_OPT_TSONLY
一起使用。当传输时间戳可用时,统计信息可在类型为 SCM_TIMESTAMPING_OPT_STATS
的单独控制消息中获取,作为 TLV(struct nlattr)类型的列表。这些统计信息允许应
用程序将各种传输层统计信息与传输时间戳关联,例如某个数据块被 peer 的接收窗口限
制了多长时间。
SOF_TIMESTAMPING_OPT_PKTINFO:
启用 SCM_TIMESTAMPING_PKTINFO 控制消息以接收带有硬件时间戳的数据包。
消息包含 struct scm_ts_pktinfo,它提供接收数据包的实际接口索引和层 2 长度。
只有在 CONFIG_NET_RX_BUSY_POLL 启用且驱动程序使用 NAPI 时,才会返回非零的
有效接口索引。该结构还包含另外两个字段,但它们是保留字段且未定义。
SOF_TIMESTAMPING_OPT_TX_SWHW:
请求在 SOF_TIMESTAMPING_TX_HARDWARE 和 SOF_TIMESTAMPING_TX_SOFTWARE
同时启用时,为传出数据包生成硬件和软件时间戳。如果同时生成两个时间戳,两个单
独的消息将回环到套接字的错误队列,每个消息仅包含一个时间戳。
SOF_TIMESTAMPING_OPT_RX_FILTER:
过滤掉虚假接收时间戳:仅当匹配的时间戳生成标志已启用时才报告接收时间戳。
接收时间戳在入口路径中生成较早,在数据包的目的套接字确定之前。如果任何套接
字启用接收时间戳,所有套接字的数据包将接收时间戳数据包。包括那些请求时间戳
报告与 SOF_TIMESTAMPING_SOFTWARE 和/或 SOF_TIMESTAMPING_RAW_HARDWARE,
但未请求接收时间戳生成。这可能发生在仅请求发送时间戳时。
接收虚假时间戳通常是无害的。进程可以忽略意外的非零值。但它使行为在其他套接
字上微妙地依赖。此标志隔离套接字以获得更确定的行为。
新应用程序鼓励传递 SOF_TIMESTAMPING_OPT_ID 以区分时间戳并传递
SOF_TIMESTAMPING_OPT_TSONLY 以操作,而不管 sysctl net.core.tstamp_allow_data
的设置。
例外情况是当进程需要额外的 cmsg 数据时,例如 SOL_IP/IP_PKTINFO 以检测出
口网络接口。然后传递选项 SOF_TIMESTAMPING_OPT_CMSG。此选项依赖于访问原
始数据包的内容,因此不能与 SOF_TIMESTAMPING_OPT_TSONLY 组合。
1.3.4. 通过控制消息启用时间戳
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
除了套接字选项外,时间戳生成还可以通过 cmsg 按写入请求,仅适用于
SOF_TIMESTAMPING_TX_*(见第 1.3.1 节)。使用此功能,应用程序可以无需启用和
禁用时间戳即可采样每个 sendmsg() 的时间戳::
struct msghdr *msg;
...
cmsg = CMSG_FIRSTHDR(msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SO_TIMESTAMPING;
cmsg->cmsg_len = CMSG_LEN(sizeof(__u32));
*((__u32 *) CMSG_DATA(cmsg)) = SOF_TIMESTAMPING_TX_SCHED |
SOF_TIMESTAMPING_TX_SOFTWARE |
SOF_TIMESTAMPING_TX_ACK;
err = sendmsg(fd, msg, 0);
通过 cmsg 设置的 SOF_TIMESTAMPING_TX_* 标志将覆盖通过 setsockopt 设置的
SOF_TIMESTAMPING_TX_* 标志。
此外,应用程序仍然需要通过 setsockopt 启用时间戳报告以接收时间戳::
__u32 val = SOF_TIMESTAMPING_SOFTWARE |
SOF_TIMESTAMPING_OPT_ID /* 或任何其他标志 */;
err = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val));
1.4 字节流时间戳
----------------
SO_TIMESTAMPING 接口支持字节流的时间戳。每个请求解释为请求当整个缓冲区内容
通过时间戳点时。也就是说,对于流选项 SOF_TIMESTAMPING_TX_SOFTWARE 将记录
当所有字节都到达设备驱动程序时,无论数据被转换成多少个数据包。
一般来说,字节流没有自然分隔符,因此将时间戳与数据相关联是非平凡的。字节范围
可能跨段,任何段可能合并(可能合并先前分段缓冲区关联的独立 send() 调用)。段
可以重新排序,同一字节范围可以在多个段中并存,对于实现重传的协议。
所有时间戳必须实现相同的语义,否则它们是不可比较的。以不同于简单情况(缓冲区
到 skb 的 1:1 映射)的方式处理“罕见”角落情况是不够的,因为性能调试通常需要
关注这些异常。
在实践中,时间戳可以与字节流段一致地关联,如果时间戳语义和测量时序的选择正确。
此挑战与决定 IP 分片策略没有不同。在那里,定义是仅对第一个分片进行时间戳。对
于字节流,我们选择仅在所有字节通过某个点时生成时间戳。SOF_TIMESTAMPING_TX_ACK
定义的实现和推理是容易的。一个需要考虑 SACK 的实现会更复杂,因为可能存在传输
空洞和乱序到达。
在主机上,TCP 也可以通过 Nagle、cork、autocork、分段和 GSO 打破简单的 1:1
缓冲区到 skbuff 映射。实现确保在所有情况下都正确,通过跟踪每个 send() 传递
给send() 的最后一个字节,即使它在 skbuff 扩展或合并操作后不再是最后一个字
节。它存储相关的序列号在 skb_shinfo(skb)->tskey。因为一个 skbuff 只有一
个这样的字段,所以只能生成一个时间戳。
在罕见情况下,如果两个请求折叠到同一个 skb,则时间戳请求可能会被错过。进程可
以通过始终在请求之间刷新 TCP 栈来检测此情况,例如启用 TCP_NODELAY 和禁用
TCP_CORK和 autocork。在 linux-4.7 之后,更好的预防合并方法是使用 MSG_EOR
标志在sendmsg()时。
这些预防措施确保时间戳仅在所有字节通过时间戳点时生成,假设网络栈本身不会重新
排序段。栈确实试图避免重新排序。唯一的例外是管理员控制:可以构造一个数据包调
度器配置,将来自同一流的不同段延迟不同。这种设置通常不常见。
2 数据接口
==========
时间戳通过 recvmsg() 的辅助数据功能读取。请参阅 `man 3 cmsg` 了解此接口的
详细信息。套接字手册页面 (`man 7 socket`) 描述了如何检索SO_TIMESTAMP 和
SO_TIMESTAMPNS 生成的数据包时间戳。
2.1 SCM_TIMESTAMPING 记录
-------------------------
这些时间戳在 cmsg_level SOL_SOCKET、cmsg_type SCM_TIMESTAMPING 和类型为
对于 SO_TIMESTAMPING_OLD::
struct scm_timestamping {
struct timespec ts[3];
};
对于 SO_TIMESTAMPING_NEW::
struct scm_timestamping64 {
struct __kernel_timespec ts[3];
始终使用 SO_TIMESTAMPING_NEW 时间戳以始终获得 struct scm_timestamping64
格式的时间戳。
SO_TIMESTAMPING_OLD 在 32 位机器上 2038 年后返回错误的时间戳。
该结构可以返回最多三个时间戳。这是一个遗留功能。任何时候至少有一个字
段不为零。大多数时间戳都通过 ts[0] 传递。硬件时间戳通过 ts[2] 传递。
ts[1] 以前用于存储硬件时间戳转换为系统时间。相反,将硬件时钟设备直接
暴露为HW PTP时钟源,以允许用户空间进行时间转换,并可选地与用户空间
PTP 堆栈(如linuxptp)同步系统时间。对于 PTP 时钟 API,请参阅
Documentation/driver-api/ptp.rst。
注意,如果同时启用了 SO_TIMESTAMP 或 SO_TIMESTAMPNS 与
SO_TIMESTAMPING 使用 SOF_TIMESTAMPING_SOFTWARE,在 recvmsg()
调用时会生成一个虚假的软件时间戳,并传递给 ts[0] 当真实软件时间戳缺
失时。这也发生在硬件传输时间戳上。
2.1.1 传输时间戳与 MSG_ERRQUEUE
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
对于传输时间戳,传出数据包回环到套接字的错误队列,并附加发送时间戳(s)。
进程通过调用带有 MSG_ERRQUEUE 标志的 recvmsg() 接收时间戳,并传递
一个足够大的 msg_control缓冲区以接收相关的元数据结构。recvmsg 调用
返回原始传出数据包,并附加两个辅助消息。
一个 cm_level SOL_IP(V6) 和 cm_type IP(V6)_RECVERR 嵌入一个
struct sock_extended_err这定义了错误类型。对于时间戳,ee_errno
字段是 ENOMSG。另一个辅助消息将具有 cm_level SOL_SOCKET 和 cm_type
SCM_TIMESTAMPING。这嵌入了 struct scm_timestamping。
2.1.1.2 时间戳类型
~~~~~~~~~~~~~~~~~~
三个 struct timespec 的语义由 struct sock_extended_err 中的
ee_info 字段定义。它包含一个类型 SCM_TSTAMP_* 来定义实际传递给
scm_timestamping 的时间戳。
SCM_TSTAMP_* 类型与之前讨论的 SOF_TIMESTAMPING_* 控制字段完全
匹配,只有一个例外对于遗留原因,SCM_TSTAMP_SND 等于零,可以设置为
SOF_TIMESTAMPING_TX_HARDWARE 和 SOF_TIMESTAMPING_TX_SOFTWARE。
它是第一个,如果 ts[2] 不为零,否则是第二个,在这种情况下,时间戳存
储在ts[0] 中。
2.1.1.3 分片
~~~~~~~~~~~~
传出数据报分片很少见,但可能发生,例如通过显式禁用 PMTU 发现。如果
传出数据包被分片,则仅对第一个分片进行时间戳,并返回给发送套接字。
2.1.1.4 数据包负载
~~~~~~~~~~~~~~~~~~
调用应用程序通常不关心接收它传递给堆栈的整个数据包负载:套接字错误队
列机制仅是一种将时间戳附加到其上的方法。在这种情况下,应用程序可以选
择读取较小的数据报,甚至长度为 0。负载相应地被截断。直到进程调用
recvmsg() 到错误队列,然而,整个数据包仍在队列中,占用 SO_RCVBUF 预算。
2.1.1.5 阻塞读取
~~~~~~~~~~~~~~~~
从错误队列读取始终是非阻塞操作。要阻塞等待时间戳,请使用 poll 或
select。poll() 将在 pollfd.revents 中返回 POLLERR,如果错误队列
中有数据。没有必要在 pollfd.events中传递此标志。此标志在请求时被忽
略。另请参阅 `man 2 poll`。
2.1.2 接收时间戳
^^^^^^^^^^^^^^^^
在接收时,没有理由从套接字错误队列读取。SCM_TIMESTAMPING 辅助数据与
数据包数据一起通过正常 recvmsg() 发送。由于这不是套接字错误,它不伴
随消息 SOL_IP(V6)/IP(V6)_RECVERROR。在这种情况下,struct
scm_timestamping 中的三个字段含义隐式定义。ts[0] 在设置时包含软件
时间戳,ts[1] 再次被弃用,ts[2] 在设置时包含硬件时间戳。
3. 硬件时间戳配置:ETHTOOL_MSG_TSCONFIG_SET/GET
===============================================
硬件时间戳也必须为每个设备驱动程序初始化,该驱动程序预期执行硬件时间戳。
参数在 include/uapi/linux/net_tstamp.h 中定义为::
struct hwtstamp_config {
int flags; /* 目前没有定义的标志,必须为零 */
int tx_type; /* HWTSTAMP_TX_* */
int rx_filter; /* HWTSTAMP_FILTER_* */
};
期望的行为通过 tsconfig netlink 套接字 ``ETHTOOL_MSG_TSCONFIG_SET``
传递到内核,并通过 ``ETHTOOL_A_TSCONFIG_TX_TYPES``、
``ETHTOOL_A_TSCONFIG_RX_FILTERS`` 和 ``ETHTOOL_A_TSCONFIG_HWTSTAMP_FLAGS``
netlink 属性设置 struct hwtstamp_config 相应地。
``ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER`` netlink 嵌套属性用于选择
硬件时间戳的来源。它由设备源的索引和时间戳类型限定符组成。
驱动程序可以自由使用比请求更宽松的配置。预期驱动程序应仅实现可以直接支持的
最通用模式。例如,如果硬件可以支持 HWTSTAMP_FILTER_PTP_V2_EVENT,则它
通常应始终升级HWTSTAMP_FILTER_PTP_V2_L2_SYNC,依此类推,因为
HWTSTAMP_FILTER_PTP_V2_EVENT 更通用(更实用)。
支持硬件时间戳的驱动程序应更新 struct,并可能返回更宽松的实际配置。如果
请求的数据包无法进行时间戳,则不应更改任何内容,并返回 ERANGE(与 EINVAL
相反,这表明 SIOCSHWTSTAMP 根本不支持)。
只有具有管理权限的进程才能更改配置。用户空间负责确保多个进程不会相互干扰,
并确保设置被重置。
任何进程都可以通过请求 tsconfig netlink 套接字 ``ETHTOOL_MSG_TSCONFIG_GET``
读取实际配置。
遗留配置是使用 ioctl(SIOCSHWTSTAMP) 与指向 struct ifreq 的指针,其
ifr_data指向 struct hwtstamp_config。tx_type 和 rx_filter 是驱动
程序期望执行的提示。如果请求的细粒度过滤对传入数据包不支持,驱动程序可能
会对请求的数据包进行时间戳。ioctl(SIOCGHWTSTAMP) 以与
ioctl(SIOCSHWTSTAMP) 相同的方式使用。然而,并非所有驱动程序都实现了这一点。
::
/* 可能的 hwtstamp_config->tx_type 值 */
enum {
/*
* 不会需要硬件时间戳的传出数据包;
* 如果数据包到达并请求它,则不会进行硬件时间戳
*/
HWTSTAMP_TX_OFF,
/*
* 启用传出数据包的硬件时间戳;
* 数据包的发送者决定哪些数据包需要时间戳,
* 在发送数据包之前设置 SOF_TIMESTAMPING_TX_SOFTWARE
*/
HWTSTAMP_TX_ON,
};
/* 可能的 hwtstamp_config->rx_filter 值 */
enum {
/* 时间戳不传入任何数据包 */
HWTSTAMP_FILTER_NONE,
/* 时间戳任何传入数据包 */
HWTSTAMP_FILTER_ALL,
/* 返回值:时间戳所有请求的数据包加上一些其他数据包 */
HWTSTAMP_FILTER_SOME,
/* PTP v1,UDP,任何事件数据包 */
HWTSTAMP_FILTER_PTP_V1_L4_EVENT,
/* 有关完整值列表,请检查
* 文件 include/uapi/linux/net_tstamp.h
*/
};
3.1 硬件时间戳实现:设备驱动程序
--------------------------------
支持硬件时间戳的驱动程序必须支持 ndo_hwtstamp_set NDO 或遗留 SIOCSHWTSTAMP
ioctl 并更新提供的 struct hwtstamp_config 与实际值,如 SIOCSHWTSTAMP 部分
所述。它还应支持 ndo_hwtstamp_get 或遗留 SIOCGHWTSTAMP。
接收数据包的时间戳必须存储在 skb 中。要获取 skb 的共享时间戳结构,请调用
skb_hwtstamps()。然后设置结构中的时间戳::
struct skb_shared_hwtstamps {
/* 硬件时间戳转换为自任意时间点的持续时间
* 自定义点
*/
ktime_t hwtstamp;
};
传出数据包的时间戳应按如下方式生成:
- 在 hard_start_xmit() 中,检查 (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)
是否不为零。如果是,则驱动程序期望执行硬件时间戳。
- 如果此 skb 和请求都可能,则声明驱动程序正在执行时间戳,通过设置 skb_shinfo(skb)->tx_flags
中的标志SKBTX_IN_PROGRESS,例如::
skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
您可能希望保留与 skb 关联的指针,而不是释放 skb。不支持硬件时间戳的驱
动程序不会这样做。驱动程序绝不能触及 sk_buff::tstamp!它用于存储网络
子系统生成的软件时间戳。
- 驱动程序应在尽可能接近将 sk_buff 传递给硬件时调用 skb_tx_timestamp()。
skb_tx_timestamp()提供软件时间戳(如果请求),并且硬件时间戳不可用
(SKBTX_IN_PROGRESS 未设置)。
- 一旦驱动程序发送数据包并/或获取硬件时间戳,它就会通过 skb_tstamp_tx()
传递时间戳,原始 skb,原始硬件时间戳。skb_tstamp_tx() 克隆原始 skb 并
添加时间戳,因此原始 skb 现在必须释放。如果获取硬件时间戳失败,则驱动程序
不应回退到软件时间戳。理由是,这会在处理管道中的稍后时间发生,而不是其他软
件时间戳,因此可能导致时间戳之间的差异。
3.2 堆叠 PTP 硬件时钟的特殊考虑
-------------------------------
在数据包的路径中可能存在多个 PHC(PTP 硬件时钟)。内核没有明确的机制允许用
户选择用于时间戳以太网帧的 PHC。相反,假设最外层的 PHC 始终是最优的,并且
内核驱动程序协作以实现这一目标。目前有 3 种堆叠 PHC 的情况,如下所示:
3.2.1 DSA(分布式交换架构)交换机
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
这些是具有一个端口连接到(完全不知情的)主机以太网接口的以太网交换机,并且
执行端口多路复用或可选转发加速功能。每个 DSA 交换机端口在用户看来都是独立的
(虚拟)网络接口,其网络 I/O 在底层通过主机接口(在 TX 上重定向到主机端口,
在 RX 上拦截帧)执行。
当 DSA 交换机连接到主机端口时,PTP 同步必须受到限制,因为交换机的可变排队
延迟引入了主机端口与其 PTP 伙伴之间的路径延迟抖动。因此,一些 DSA 交换机
包含自己的时间戳时钟,并具有在自身 MAC上执行网络时间戳的能力,因此路径延迟
仅测量线缆和 PHY 传播延迟。支持 Linux 的 DSA 交换机暴露了与任何其他网络
接口相同的 ABI(除了 DSA 接口在网络 I/O 方面实际上是虚拟的,它们确实有自
己的PHC)。典型地,但不是强制性地,所有DSA 交换机接口共享相同的 PHC。
通过设计,DSA 交换机对连接到其主机端口的 PTP 时间戳不需要任何特殊的驱动程
序处理。然而,当主机端口也支持 PTP 时间戳时,DSA 将负责拦截
``.ndo_eth_ioctl`` 调用,并阻止尝试在主机端口上启用硬件时间戳。这是因为
SO_TIMESTAMPING API 不允许为同一数据包传递多个硬件时间戳,因此除了 DSA
交换机端口之外的任何人都不应阻止这样做。
在通用层,DSA 提供了以下基础设施用于 PTP 时间戳:
- ``.port_txtstamp()``:在用户空间从用户空间请求带有硬件 TX 时间戳请求
的数据包之前调用的钩子。这是必需的,因为硬件时间戳在实际 MAC 传输后才可
用,因此驱动程序必须准备将时间戳与原始数据包相关联,以便它可以重新入队数
据包到套接字的错误队列。为了保存可能在时间戳可用时需要的数据包,驱动程序
可以调用 ``skb_clone_sk``,在 skb->cb 中保存克隆指针,并入队一个 tx
skb 队列。通常,交换机会有一个PTP TX 时间戳寄存器(或有时是一个 FIFO),
其中时间戳可用。在 FIFO 的情况下,硬件可能会存储PTP 序列 ID/消息类型/
域号和实际时间戳的键值对。为了在等待时间戳的数据包队列和实际时间戳之间正
确关联,驱动程序可以使用 BPF 分类器(``ptp_classify_raw``) 来识别 PTP
传输类型,并使用 ``ptp_parse_header`` 解释 PTP 头字段。可能存在一个 IRQ,
当此时间戳可用时触发,或者驱动程序可能需要轮询,在调用 ``dev_queue_xmit()``
到主机接口之后。单步 TX 时间戳不需要数据包克隆,因为 PTP 协议不需要后续消
息(因为TX 时间戳已嵌入到数据包中),因此用户空间不期望数据包带有 TX 时间戳
被重新入队到其套接字的错误队列。
- ``.port_rxtstamp()``:在 RX 上,DSA 运行 BPF 分类器以识别 PTP 事件消息
(任何其他数据包,包括 PTP 通用消息,不进行时间戳)。驱动程序提供原始(也是唯一)
时间戳数据包,以便它可以标记它,如果它是立即可用的,或者延迟。在接收时,时间
戳可能要么在频带内(通过DSA 头中的元数据,或以其他方式附加到数据包),要么在频
带外(通过另一个 RX 时间戳FIFO)。在 RX 上延迟通常是必要的,当检索时间戳需要
可睡眠上下文时。在这种情况下,DSA驱动程序有责任调用 ``netif_rx()`` 在新鲜时
间戳的 skb 上。
3.2.2 以太网 PHYs
^^^^^^^^^^^^^^^^^
这些是通常在网络栈中履行第 1 层角色的设备,因此它们在 DSA 交换机中没有网络接
口的表示。然而,PHY可能能够检测和时间戳 PTP 数据包,出于性能原因:在尽可能接
近导线的地方获取的时间戳具有更稳定的同步性和更精确的精度。
支持 PTP 时间戳的 PHY 驱动程序必须创建 ``struct mii_timestamper`` 并添加
指向它的指针在 ``phydev->mii_ts`` 中。 ``phydev->mii_ts`` 的存在将由网络
堆栈检查。
由于 PHY 没有网络接口表示,PHY 的时间戳和 ethtool ioctl 操作需要通过其各自
的 MAC驱动程序进行中介。因此,与 DSA 交换机不同,需要对每个单独的 MAC 驱动
程序进行 PHY时间戳支持的修改。这包括:
- 在 ``.ndo_eth_ioctl`` 中检查,是否 ``phy_has_hwtstamp(netdev->phydev)``
为真或假。如果是,则 MAC 驱动程序不应处理此请求,而应将其传递给 PHY 使用
``phy_mii_ioctl()``。
- 在 RX 上,特殊干预可能或可能不需要,具体取决于将 skb 传递到网络堆栈的函数。
在 plain ``netif_rx()`` 和类似情况下,MAC 驱动程序必须检查是否
``skb_defer_rx_timestamp(skb)`` 是必要的,如果是,则不调用 ``netif_rx()``。
如果 ``CONFIG_NETWORK_PHY_TIMESTAMPING`` 启用,并且
``skb->dev->phydev->mii_ts`` 存在,它的 ``.rxtstamp()`` 钩子现在将被调
用,以使用与 DSA 类似的逻辑确定 RX 时间戳延迟是否必要。同样像 DSA,它成为
PHY 驱动程序的责任,在时间戳可用时发送数据包到堆栈。
对于其他 skb 接收函数,例如 ``napi_gro_receive`` 和 ``netif_receive_skb``,
堆栈会自动检查是否 ``skb_defer_rx_timestamp()`` 是必要的,因此此检查不
需要在驱动程序内部。
- 在 TX 上,同样,特殊干预可能或可能不需要。调用 ``mii_ts->txtstamp()``钩
子的函数名为``skb_clone_tx_timestamp()``。此函数可以直接调用(在这种情
况下,确实需要显式 MAC 驱动程序支持),但函数也 piggybacks 从
``skb_tx_timestamp()`` 调用,许多 MAC 驱动程序已经为软件时间戳目的执行。
因此,如果 MAC 支持软件时间戳,则它不需要在此阶段执行任何其他操作。
3.2.3 MII 总线嗅探设备
^^^^^^^^^^^^^^^^^^^^^^
这些执行与时间戳以太网 PHY 相同的角色,除了它们是离散设备,因此可以与任何 PHY
组合,即使它不支持时间戳。在 Linux 中,它们是可发现的,可以通过 Device Tree
附加到 ``struct phy_device``,对于其余部分,它们使用与那些相同的 mii_ts 基
础设施。请参阅 Documentation/devicetree/bindings/ptp/timestamper.txt 了
解更多详细信息。
3.2.4 MAC 驱动程序的其他注意事项
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
堆叠 PHC 可能会暴露 MAC 驱动程序的错误,这些错误在未堆叠 PHC 时无法触发。一个
例子涉及此行代码,已经在前面的部分中介绍过::
skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
任何 TX 时间戳逻辑,无论是普通的 MAC 驱动程序、DSA 交换机驱动程序、PHY 驱动程
序还是 MII 总线嗅探设备驱动程序,都应该设置此标志。但一个未意识到 PHC 堆叠的
MAC 驱动程序可能会被其他不是它自己的实体设置此标志,并传递一个重复的时间戳。例
如,典型的 TX 时间戳逻辑可能是将传输部分分为 2 个部分:
1. "TX":检查是否通过 ``.ndo_eth_ioctl``("``priv->hwtstamp_tx_enabled
== true``")和当前 skb 是否需要 TX 时间戳("``skb_shinfo(skb)->tx_flags
& SKBTX_HW_TSTAMP``")。如果为真,则设置 "``skb_shinfo(skb)->tx_flags
|= SKBTX_IN_PROGRESS``" 标志。注意:如上所述,在堆叠 PHC 系统中,此条件
不应触发,因为此 MAC 肯定不是最外层的 PHC。但这是典型的错误所在。传输继续
使用此数据包。
2. "TX 确认":传输完成。驱动程序检查是否需要收集任何 TX 时间戳。这里通常是典
型的错误所在:驱动程序采取捷径,只检查 "``skb_shinfo(skb)->tx_flags &
SKBTX_IN_PROGRESS``" 是否设置。在堆叠 PHC 系统中,这是错误的,因为此 MAC
驱动程序不是唯一在 TX 数据路径中启用 SKBTX_IN_PROGRESS 的实体。
此问题的正确解决方案是 MAC 驱动程序在其 "TX 确认" 部分中有一个复合检查,不仅
针对 "``skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS``",还针对
"``priv->hwtstamp_tx_enabled == true``"。因为系统确保 PTP 时间戳仅对最
外层 PHC 启用,此增强检查将避免向用户空间传递重复的 TX 时间戳。
@@ -0,0 +1,85 @@
.. SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/networking/vxlan.rst
:翻译:
范雨 Fan Yu <fan.yu9@zte.com.cn>
:校译:
- 邱禹潭 Qiu Yutan <qiu.yutan@zte.com.cn>
- 徐鑫 xu xin <xu.xin16@zte.com.cn>
==========================
虚拟扩展本地局域网协议文档
==========================
VXLAN 协议是一种隧道协议,旨在解决 IEEE 802.1q 中 VLAN ID(4096)有限的问题。
VXLAN 将标识符的大小扩展到 24 位(16777216)。
VXLAN 在 IETF RFC 7348 中进行了描述,并已由多家供应商设计实现。
该协议通过 UDP 协议运行,并使用特定目的端口。
本文档介绍了 Linux 内核隧道设备,Openvswitch 也有单独的 VXLAN 实现。
与大多数隧道不同,VXLAN 是 1 对 N 的网络,而不仅仅是点对点网络。
VXLAN 设备可以通过类似于学习桥接器的方式动态学习另一端点的 IP 地址,也可以利用静态配置的转发条目。
VXLAN 的管理方式与它的两个近邻 GRE 和 VLAN 相似。
配置 VXLAN 需要 iproute2 的版本与 VXLAN 首次向上游合并的内核版本相匹配。
1. 创建 vxlan 设备::
# ip link add vxlan0 type vxlan id 42 group 239.1.1.1 dev eth1 dstport 4789
这将创建一个名为 vxlan0 的网络设备,该设备通过 eth1 使用组播组 239.1.1.1 处理转发表中没有对应条目的流量。
目标端口号设置为 IANA 分配的值 4789,VXLAN 的 Linux 实现早于 IANA 选择标准目的端口号的时间。
因此默认使用 Linux 选择的值,以保持向后兼容性。
2. 删除 vxlan 设备::
# ip link delete vxlan0
3. 查看 vxlan 设备信息::
# ip -d link show vxlan0
使用新的 bridge 命令可以创建、销毁和显示 vxlan 转发表。
1. 创建vxlan转发表项::
# bridge fdb add to 00:17:42:8a:b4:05 dst 192.19.0.2 dev vxlan0
2. 删除vxlan转发表项::
# bridge fdb delete 00:17:42:8a:b4:05 dev vxlan0
3. 显示vxlan转发表项::
# bridge fdb show dev vxlan0
以下网络接口控制器特性可能表明对 UDP 隧道相关的卸载支持(最常见的是 VXLAN 功能,
但是对特定封装协议的支持取决于网络接口控制器):
- `tx-udp_tnl-segmentation`
- `tx-udp_tnl-csum-segmentation`
对 UDP 封装帧执行 TCP 分段卸载的能力
- `rx-udp_tunnel-port-offload`
在接收端解析 UDP 封装帧,使网络接口控制器能够执行协议感知卸载,
例如内部帧的校验和验证卸载(只有不带协议感知卸载的网络接口控制器才需要)
对于支持 `rx-udp_tunnel-port-offload` 的设备,可使用 `ethtool` 查询当前卸载端口的列表::
$ ethtool --show-tunnels eth0
Tunnel information for eth0:
UDP port table 0:
Size: 4
Types: vxlan
No entries
UDP port table 1:
Size: 4
Types: geneve, vxlan-gpe
Entries (1):
port 1230, vxlan-gpe
@@ -0,0 +1,126 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/networking/xfrm_proc.rst
:翻译:
王亚鑫 Wang Yaxin <wang.yaxin@zte.com.cn>
=================================
XFRM proc - /proc/net/xfrm_* 文件
=================================
作者:Masahide NAKAMURA <nakam@linux-ipv6.org>
转换统计信息
------------
`xfrm_proc` 提供一组统计计数器,显示转换过程中丢弃的数据包及其原因。
这些计数器属于Linux私有MIB的一部分,可通过 `/proc/net/xfrm_stat`
查看。
入站错误
~~~~~~~~
XfrmInError:
未匹配其他类别的所有错误
XfrmInBufferError:
缓冲区不足
XfrmInHdrError:
头部错误
XfrmInNoStates:
未找到状态
(入站SPI、地址或SA的IPsec协议不匹配)
XfrmInStateProtoError:
转换协议相关的错误
(如SA密钥错误)
XfrmInStateModeError:
转换模式相关的错误
XfrmInStateSeqError:
序列号错误
序列号超出窗口范围
XfrmInStateExpired:
状态已过期
XfrmInStateMismatch:
状态选项不匹配
(如UDP封装类型不匹配)
XfrmInStateInvalid:
无效状态
XfrmInTmplMismatch:
状态模板不匹配
(如入站SA正确但SP规则错误)
XfrmInNoPols:
未找到状态的对应策略
(如入站SA正确但无SP规则)
XfrmInPolBlock:
丢弃的策略
XfrmInPolError:
错误的策略
XfrmAcquireError:
状态未完全获取即被使用
XfrmFwdHdrError:
转发路由禁止
XfrmInStateDirError:
状态方向不匹配
(输入路径查找到输出状态,预期是输入状态或者无方向)
出站错误
~~~~~~~~
XfrmOutError:
未匹配其他类别的所有错误
XfrmOutBundleGenError:
捆绑包生成错误
XfrmOutBundleCheckError:
捆绑包校验错误
XfrmOutNoStates:
未找到状态
XfrmOutStateProtoError:
转换协议特定错误
XfrmOutStateModeError:
转换模式特定错误
XfrmOutStateSeqError:
序列号错误
(序列号溢出)
XfrmOutStateExpired:
状态已过期
XfrmOutPolBlock:
丢弃策略
XfrmOutPolDead:
失效策略
XfrmOutPolError:
错误策略
XfrmOutStateInvalid:
无效状态(可能已过期)
XfrmOutStateDirError:
状态方向不匹配(输出路径查找到输入状态,预期为输出状态或无方向)