使用 Onload
本页面介绍了如何将 Onload 与 U4 Compute Engine 实例搭配使用。
关于 Onload
Onload 是一种高性能网络 堆栈,适用于需要超低延迟、最小 抖动和一致性能的延迟敏感型应用。Onload 提供了一种 TCP/IP 实现,该实现绕过操作系统内核并直接在用户空间中运行,同时允许应用使用标准 BSD 套接字 API。
将 Onload 与 ULL Solution 搭配使用时,支持以下功能:
准备工作
在 U4 Compute Engine 实例上使用 Onload 之前,您必须满足以下要求。
创建 U4 实例
如果尚未创建 U4 Compute Engine 实例,请使用以下任一过程创建,这些过程包含 Onload 所需的配置:
- 如需创建 U4P 或 U4C 裸金属实例,请参阅 创建 ULL Compute Engine 实例。
- 如需创建 U4S 虚拟机 (VM) 实例,请参阅 为辅助工作负载创建非 ULL Compute Engine 实例。
使用 SSH 连接到您的实例
如果尚未连接到您的实例,请使用 SSH 连接。
切换到根用户
以下过程中的命令和脚本会修改系统级设置、内核参数和网络接口。如需成功运行这些命令和脚本,您必须以根用户身份执行它们。您可以通过
运行 sudo su 切换到根 shell,也可以根据需要添加 sudo,然后再运行命令。
设置 Onload
本部分介绍了在 U4 实例上设置 Onload 所需的步骤。
安装依赖项
如果您使用的是 Rocky Linux,请启用 CodeReady Builder (CRB) 代码库。如果您使用的是 Red Hat Enterprise Linux (RHEL),请跳过此步骤。
dnf -y config-manager --enable crb
安装 Onload 所需的依赖项:
dnf -y install git clang \ python3-setuptools \ linuxptp \ libcap-devel libbpf-devel libxdp-devel
拉取 Onload 源代码
如需拉取包含所需更改的 onload 代码库
,请运行以下命令:
umask 0022 mkdir -p /usr/src/ git clone https://github.com/Xilinx-CNS/onload /usr/src/onload # 9.2.0.43 / 9.2.1, origin/v9_2 as of May 18, 2026 git -C /usr/src/onload checkout origin/v9_2 # Pull Google-specific Onload changes not yet merged as of v9_2 curl -L https://github.com/Xilinx-CNS/onload/pull/279.patch | git -C /usr/src/onload am curl -L https://github.com/Xilinx-CNS/onload/pull/282.patch | git -C /usr/src/onload am curl -L https://github.com/Xilinx-CNS/onload/pull/325.patch | git -C /usr/src/onload am curl -L https://github.com/Xilinx-CNS/onload/pull/327.patch | git -C /usr/src/onload am
构建 Onload
如需构建 Onload,请运行以下命令:
cd /usr/src/onload USEONLOADEXT=1 ./scripts/onload_install --no-sfc pushd ./src/tools/bpf_link_helper clang xdp_onload_prepare.c -lbpf -o xdp_onload_prepare clang -target bpf -O2 -g -c xdp_tstamp.c -o ./xdp_tstamp.o popd
停用间接分支跟踪 (IBT)
必须停用 IBT,才能使用 Onload,如 间接分支跟踪 (IBT) 不兼容 中所述。
如需停用 IBT,请运行以下命令:
grubby --args="ibt=off" --update-kernel=ALL reboot
加载 Onload
本部分介绍了如何在实例上加载 Onload。
在 U4P 或 U4C 实例上加载 Onload
如需在 U4P 或 U4C 裸金属实例上加载 Onload,请使用以下脚本。
IFNAMES=($( find /sys/class/net -type l -not -lname '*virtual*' -printf '%l %f\n' | sort | awk '{print $2}')) for IFNAME in "${IFNAMES[@]}"; do ethtool -L "${IFNAME}" rx 16 tx 16 ethtool -G "${IFNAME}" rx 1024 rx-buf-len 2048 ethtool -K "${IFNAME}" ntuple on echo 0 > "/sys/class/net/${IFNAME}/threaded" /usr/src/onload/src/tools/bpf_link_helper/xdp_onload_prepare \ "${IFNAME}" /usr/src/onload/src/tools/bpf_link_helper/xdp_tstamp.o done setenforce 0 numactl --cpunodebind=0,2 onload_tool reload --onload-only for IFNAME in "${IFNAMES[@]}"; do echo "${IFNAME}" 16 > /sys/module/sfc_resource/afxdp/register until [[ $(cat "/sys/class/net/${IFNAME}/carrier") == 1 ]]; do sleep 1 done hwstamp_ctl -i "${IFNAME}" -r 1 done echo 1 > /sys/module/sfc_resource/parameters/enable_af_xdp_flow_filters echo 256 > /sys/module/onload/parameters/xdp_headroom echo -1 > /sys/module/onload/parameters/inject_kernel_gid
在 U4S 实例上加载 Onload
如需在 U4S 虚拟机实例上加载 Onload,请使用以下脚本。
IFNAME=NIC_NAME ALLOCATED_QUEUES=ALLOCATED_QUEUES ethtool -L "$IFNAME" rx "${ALLOCATED_QUEUES}" tx "${ALLOCATED_QUEUES}" ethtool -G "$IFNAME" rx 1024 rx-buf-len 2048 ethtool -K "$IFNAME" ntuple on echo 0 > "/sys/class/net/${IFNAME}/threaded" /usr/src/onload/src/tools/bpf_link_helper/xdp_onload_prepare "$IFNAME" \ /usr/src/onload/src/tools/bpf_link_helper/xdp_tstamp.o setenforce 0 numactl --cpunodebind=0 onload_tool reload --onload-only echo "${IFNAME} ${ALLOCATED_QUEUES}" | tee /sys/module/sfc_resource/afxdp/register until [[ $(cat "/sys/class/net/${IFNAME}/carrier") == 1 ]]; do sleep 1 done hwstamp_ctl -i "$IFNAME" -r 1 echo 1 > /sys/module/sfc_resource/parameters/enable_af_xdp_flow_filters echo 256 > /sys/module/onload/parameters/xdp_headroom echo -1 > /sys/module/onload/parameters/inject_kernel_gid
替换以下内容:
NIC_NAME:网络接口的操作系统名称,例如enp22s0f0。ALLOCATED_QUEUES:要在网络接口上为 Onload 分配的接收 (RX) 和传输 (TX) 队列的数量。将此值设置为分配给 vNIC 的 RX 或 TX 队列总数的一半。对于 U4S 实例,队列总数(分别为 RX 或 TX 队列)等于
num_vcpus / num_vnics,每个 vNIC 最多16个队列。 例如,如果 vNIC 有4个 TX 队列,请将此值设置为2。 如果 vNIC 有16个 TX 队列,请将此值设置为8。如需详细了解默认队列分配,请参阅 接收和传输队列。
配置 Onload 标志
如需优化性能并帮助减少延迟,您可以在使用 Onload 运行应用时使用以下环境变量和标志集。本部分包含建议的设置,您可以根据应用的需要进行调整。
您必须在应用命令之前指定这些参数。例如,如需使用这些设置运行应用,请使用以下格式:
env EF_NO_FAIL=0 \ EF_POLL_USEC=100000 \ EF_RX_TIMESTAMPING=3 \ EF_MAX_ENDPOINTS=1048576 \ EF_WODA_SINGLE_INTERFACE=1 \ EF_UL_EPOLL=3 \ EF_USE_HUGE_PAGES=0 \ EF_EPOLL_CTL_HANDOFF=0 \ EF_FDS_MT_SAFE=0 \ EF_NONAGLE_INFLIGHT_MAX=-1 \ EF_RXQ_SIZE=4096 \ EF_TCP_RCVBUF_ESTABLISHED_DEFAULT=65536 \ EF_MAX_PACKETS=65536 \ EF_PREFAULT_PACKETS=65536 \ EF_EVS_PER_POLL=256 \ onload -v --profile=latency APPLICATION_COMMAND
卸载 Onload
如需卸载 Onload,请使用以下脚本。
IFNAMES=($( find /sys/class/net -type l -not -lname '*virtual*' -printf '%l %f\n' | sort | awk '{print $2}')) for IFNAME in "${IFNAMES[@]}"; do rm -f "/sys/fs/bpf/onload_xdp_xsk_${IFNAME}" done onload_tool unload --onload-only for IFNAME in "${IFNAMES[@]}"; do # (optional) Disable threaded busypolling in case it's up. See busypolling # section echo 0 > "/sys/class/net/${IFNAME}/threaded" ip link set dev "${IFNAME}" xdp off done
配置自动启动 Onload 的 systemd 服务
如需在实例启动时自动启动 Onload,您可以将其注册为
systemd 服务。使用以下模板创建服务文件:
[Unit] Description=ULL Solution -- Loading & instance tuning for Onload After=network-online.target After=google-guest-agent-manager.service google-guest-agent.service Before=multi-user.target Before=sshd.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=START_SCRIPT_PATH ExecStartPost=OPTIMIZATION_SCRIPT_PATH ExecStop=STOP_SCRIPT_PATH [Install] WantedBy=multi-user.target
替换以下内容:
START_SCRIPT_PATH:启动 Onload 的脚本的路径, 例如加载 Load Onload 中的某个脚本。OPTIMIZATION_SCRIPT_PATH:应用优化配置的可选脚本的路径。如果需要,您可以创建一个包含性能优化的脚本,并在此处添加该脚本。否则,您可以移除包含此变量的行。STOP_SCRIPT_PATH:停止 Onload 的脚本的路径,例如卸载 Onload 中的脚本。
配置忙轮询
本部分提供了有关如何在实例上配置忙轮询的示例。
忙轮询会持续检查新的网络数据包,而不是等待设备中断,这有助于减少延迟和抖动。如需详细了解忙轮询,请参阅 Linux 内核文档 中的忙轮询。
获取 Onload 堆栈正在使用的 RX 队列
如需获取 Onload 堆栈正在使用的 RX 队列,请执行以下操作:
运行
onload_stackdump以获取 Onload 堆栈 ID:onload_stackdump
由于 Onload 堆栈 ID 和 NAPI 队列 ID 可能并不总是匹配,因此请使用以下脚本从堆栈 ID 获取相应的接口名称、索引和队列 ID。
ONLOAD_STACK=ONLOAD_STACK_ID INTF_HWPORT_MAP=($(onload_stackdump "${ONLOAD_STACK}" netif_extra | grep -oP "intf_i_to_hwport=\K.*$" | tr ',' '\n')) HWPORT_IFINDEX_MAP=($(onload_stackdump "${ONLOAD_STACK}" hwport_to_base_ifindex | grep -oP "\d+$")) while read -r INTF_ID QUEUE_ID; do HW_PORT="${INTF_HWPORT_MAP[INTF_ID]}" IFINDEX="${HWPORT_IFINDEX_MAP[HW_PORT]}" IFNAME=$(ip -j link | jq -r ".[] | select(.ifindex == ${IFINDEX}) | .ifname") echo "ifname=${IFNAME} ifindex=${IFINDEX} queue_id=${QUEUE_ID}" done < <(onload_stackdump "${ONLOAD_STACK}" netif | grep -oP "((intf|vi)=)\K\d+" | xargs -n 2)
将
ONLOAD_STACK_ID替换为要启用或停用忙轮询的堆栈的 ID。
在 RX 队列上启用忙轮询
本部分提供了一个示例,说明如何在 Onload 堆栈 正在使用的 特定 RX 队列上启用忙轮询。
在终端中运行以下 bash 脚本。
enable_single_queue函数会执行以下操作:- 使用 netlink (
ynl) 获取与 RX 队列对应的napi_id - 在
napi_id上设置threaded: busy-poll属性 - 获取忙轮询
napi_id的线程的kthread_pid - 使用
taskset将kthread_pid绑定到特定 CPU
readonly NETDEV_YAML=${NETDEV_YAML:-"/usr/share/ynl/specs/netdev.yaml"} call_ynl() { ynl --spec "${NETDEV_YAML}" "$@" } enable_single_queue() { local -r interface="$1" local -r ifindex=$(cat "/sys/class/net/${interface}/ifindex") local -r q_id="$2" local -r cpu="$3" local napi_id napi_id=$(call_ynl --output-json --do queue-get \ --json "{\"ifindex\": ${ifindex}, \"id\": ${q_id}, \"type\": \"rx\"}" | \ jq -r '."napi-id"') if [[ -z "${napi_id}" || "${napi_id}" == "null" ]]; then echo "Error: No napi_id found for queue ${q_id} on interface ${interface}" >&2 exit 1 fi echo "Enabling busypolling for queue ${q_id} (NAPI ${napi_id}) on CPU ${cpu}" call_ynl --do napi-set --json "{\"id\": \"${napi_id}\", \"threaded\": \"busy-poll\"}" >/dev/null local napi_kthread_pid napi_kthread_pid=$(call_ynl --do napi-get --output-json \ --json "{\"id\": \"${napi_id}\"}" | jq -r '."pid" // empty') if [[ -z "${napi_kthread_pid}" ]]; then echo "Error: Could not get PID for NAPI ${napi_id}" >&2 exit 1 fi taskset -pc "${cpu}" "${napi_kthread_pid}" >/dev/null }
- 使用 netlink (
运行以下命令以调用
enable_single_queue函数:enable_single_queue NIC_NAME QUEUE_ID CPU_ID
替换以下内容:
NIC_NAME:网络接口的操作系统名称,例如ens8f0。QUEUE_ID:您之前获取的队列 ID。CPU_ID:要在其上运行忙轮询线程的 CPU 的 ID,例如5。
确保您已规划可能会影响忙轮询配置的线程重新创建事件 。
规划线程重新创建事件
当内核重新创建线程时,关联的线程配置(例如 CPU 亲和性掩码和调度政策)不会保留。以下事件会导致内核重新创建忙轮询 NAPI 的线程:
- 链接翻动/重置
- XDP 程序附件(例如,在运行脚本以加载 Onload 或附加自定义 XDP 程序时)
- 环形参数更改 (
ethtool -G) - 队列计数更改 (
ethtool -L)
为避免出现问题,请考虑在正常操作期间避免执行会导致线程重新创建事件的任务。
如需在重新创建线程后保持忙轮询配置,您必须获取线程的新进程 ID (PID),并将其重新绑定到 CPU。您可以再次运行 enable_single_queue
函数来完成此操作。
在 RX 队列上停用忙轮询
本部分提供了一个示例,说明如何在 Onload 堆栈 正在使用的 特定 RX 队列上停用忙轮询。
运行
onload_stackdump以获取 Onload 堆栈正在使用的 RX 队列:onload_stackdump
在终端中运行以下 bash 脚本。
disable_single_queue函数会执行以下操作:- 使用 netlink (
ynl) 获取与 RX 队列对应的napi_id - 将
napi_id的 threaded 属性设置为disabled
disable_single_queue() { local -r interface="$1" local -r ifindex=$(cat "/sys/class/net/${interface}/ifindex") local -r q_id="$2" local napi_id napi_id=$(call_ynl --output-json --do queue-get \ --json "{\"ifindex\": ${ifindex}, \"id\": ${q_id}, \"type\": \"rx\"}" | \ jq -r '."napi-id"') if [[ -z "${napi_id}" || "${napi_id}" == "null" ]]; then echo "Error: No napi_id found for queue ${q_id} on interface ${interface}" >&2 exit 1 fi echo "Disabling busypolling for queue ${q_id} (NAPI ${napi_id})" call_ynl --do napi-set --json "{\"id\": \"${napi_id}\", \"threaded\": \"disabled\"}" >/dev/null }
- 使用 netlink (
运行以下命令以调用
disable_single_queue函数:disable_single_queue NIC_NAME QUEUE_ID
替换以下内容:
NIC_NAME:网络接口的操作系统名称,例如ens8f0。QUEUE_ID:您之前获取的队列 ID。
获取队列的忙轮询状态
如需检查队列的 NAPI 状态以查看其是否正在忙轮询,您可以使用以下命令:
IFNAME=NIC_NAME QUEUE_ID=QUEUE_ID QUEUE_TYPE=QUEUE_TYPE IFINDEX=$(cat "/sys/class/net/${IFNAME}/ifindex") NAPI_ID=$(ynl --spec /usr/share/ynl/specs/netdev.yaml \ --output-json --do queue-get \ --json '{"ifindex": '${IFINDEX}', "id": '${QUEUE_ID}', "type": "'${QUEUE_TYPE}'"}' | \ jq '."napi-id"') ynl --spec /usr/share/ynl/specs/netdev.yaml \ --output-json --do napi-get \ --json '{"id": '${NAPI_ID}'}' | jq -r '"status: \(.threaded)"'
替换以下内容:
NIC_NAME:网络接口的操作系统名称,例如ens8f0。QUEUE_ID:您要检查的队列的 ID。QUEUE_TYPE:rx或tx。
优化性能
本部分提供了有关优化 U4 裸金属实例(U4P 和 U4C)性能的一般指导。根据工作负载的需要调整此指导中的示例。
查看 U4 裸金属实例的 NUMA 拓扑
下表介绍了 U4 裸金属实例的哪些网络接口使用哪些 NUMA 节点:
| NIC(Google Cloud 名称) | NIC(操作系统名称) | NUMA 节点 | PCIE BDF |
|---|---|---|---|
nic0 |
enp22s0f0 |
0 | 0000:16:00.0 |
nic1 |
ens8f0 |
0 | 0000:27:00.0 |
nic2 |
ens48f0 |
2 | 0000:b8:00.0 |
上表包含 RHEL 的典型操作系统分配的网络接口名称。实际名称可能有所不同。
确定 CPU 隔离方案
为获得最佳性能,我们建议您隔离以下内容:
- 应用使用的 CPU
- 用于忙轮询 Onload RX 队列的 CPU
- 用于内核和驱动程序中断的 CPU
下表提供了一个示例,说明如何在 U4 裸金属实例上隔离 CPU。根据工作负载的需要调整映射,例如,您可能需要更多应用 CPU。
| 用途 | CPU |
|---|---|
| 常规内核中断 | 0,1,30,31,60,61,90,91 |
nic0 驱动程序中断(队列 0-11) |
2 |
nic1 驱动程序中断(队列 0-11) |
3 |
nic0 和 nic1 驱动程序中断(队列 12-15) |
4 |
nic1 忙轮询 |
5-16 |
nic1 应用线程 (Onload) |
17-29 |
nic0 忙轮询 |
32-43 |
nic0 应用线程 (Onload) |
44-59 |
nic2 驱动程序中断(队列 0-11) |
62 |
nic2 驱动程序中断(队列 12-15) |
63 |
nic2 忙轮询 |
64-75 |
nic2 应用线程 (Onload) |
76-89 |
安装用于性能优化的依赖项
如需安装用于性能优化的所需依赖项,请运行以下命令:
dnf -y install numactl tuna jq
配置内核启动参数
如需将 CPU 与内核调度隔离,请运行以下命令。这还会停用 Intel QuickAssist Technology (QAT),使其不会干扰隔离的内核。
以下示例命令会隔离 CPU 2-29、32-59 和 62-89,并将 0,1,30,31,60,61,90,91 指定为常规内核中断。这些值
与示例 CPU 隔离方案对应。根据 CPU 隔离方案的需要替换这些值。
grubby --args="isolcpus=domain,managed_irq,2-29,32-59,62-89 nohz=on nohz_full=2-29,32-59,62-89 rcu_nocbs=2-29,32-59,62-89 irqaffinity=0,1,30,31,60,61,90,91 rcu_nocb_poll modprobe.blacklist=intel_qat,qat_4xxx" --update-kernel=ALL reboot
配置启动后 CPU 隔离
如需在启动后隔离 CPU,请运行以下命令。这些值 与示例 CPU 隔离方案对应。根据 CPU 隔离方案的需要替换这些值。
tuna isolate -c 2-29,32-59,62-89
将队列中断分配给特定 CPU
本部分介绍了如何将 gve 队列中断请求 (IRQ) 移至特定 CPU。gve 驱动程序由 GVNIC 网络接口
类型使用 Google Cloud。
确定给定网络接口和队列范围的 IRQ。请参阅以下 bash 示例,该示例定义了
irq_list函数。irq_list() { local ifname=$1 local queue_begin=$2 local queue_end=$3 pci_name=$(basename $(readlink /sys/class/net/${ifname}/device)) rx_ntfy_blk_start=$(ethtool -l "${ifname}" | awk ' /Pre-set maximums:/ { in_preset = 1 } /Current hardware settings:/ { in_preset = 0 } in_preset && $1 == "RX:" { rx = $2 } in_preset && $1 == "TX:" { tx = $2 } END { print int((rx + tx) / 2) } ') for i in $(seq "${queue_begin}" "${queue_end}"); do irq_tx="gve-ntfy-blk${i}@pci:${pci_name}" irq_rx="gve-ntfy-blk$(($i + rx_ntfy_blk_start))@pci:${pci_name}" # gve IRQ names are stored in a char[IFNAMSIZ + 16] so capped to 31 characters. echo "${irq_tx:0:31}" echo "${irq_rx:0:31}" done | paste -sd ',' }
根据 CPU 隔离方案将 IRQ 分配给相应的 CPU。 以下示例脚本使用了
tuna和上一步中的irq_list函数:tuna move -c 2 -q "$(irq_list enp22s0f0 0 11)" tuna move -c 4 -q "$(irq_list enp22s0f0 12 15)" tuna move -c 3 -q "$(irq_list ens8f0 0 11)" tuna move -c 4 -q "$(irq_list ens8f0 12 15)" tuna move -c 62 -q "$(irq_list ens48f0 0 11)" tuna move -c 63 -q "$(irq_list ens48f0 12 15)"
配置操作系统和设备设置
运行以下脚本来配置有助于最大限度减少延迟并防止默认操作系统行为干扰 Onload 配置的设置。
echo 0 > /proc/sys/net/core/busy_poll echo 0 > /proc/sys/net/core/busy_read echo 0 > /proc/sys/kernel/timer_migration echo 0 > /proc/sys/net/core/rps_sock_flow_entries echo -1 > /proc/sys/kernel/sched_rt_runtime_us for IFNAME in "${IFNAMES[@]}"; do ethtool -C "${IFNAME}" rx-usecs 0 tx-usecs 0 echo 0 > "/sys/class/net/${IFNAME}/napi_defer_hard_irqs" echo 15000 > "/sys/class/net/${IFNAME}/gro_flush_timeout" done
如需将流量从专用于 Onload 工作负载的队列中转向, 请使用 RSS (
ethtool -X)。以下示例脚本基于 示例 CPU 隔离方案中的值。由于 Onload 使用队列0-11,因此该脚本会将所有其他流量定向到队列12-15。for IFNAME in "${IFNAMES[@]}"; do ethtool -X "${IFNAME}" weight 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 done
后续步骤
- 如需将实例系统时钟与主机服务器的物理 NIC 时钟同步,请参阅配置准确时间。