XDP/BPF:是否有 user-space 替代 `bpf_ktime_get_ns`?
XDP/BPF: Is there an user-space alternative to `bpf_ktime_get_ns`?
我想在我的 XDP 程序中收到的数据包中插入一个时间戳。我知道如何获取时间戳的唯一方法是调用 bpf_ktime_get_ns
.
但是创建可比时间戳的 user-space 等效函数是什么?据我所知,ktime_get_ns
returns 自系统启动以来的时间(以纳秒为单位)。有
$ uptime
11:45:35 up 2 days, 3:15, 3 users, load average: 0.19, 0.29, 0.27
但这只是 returns 自系统启动以来的时间 秒 。所以这里不可能进行精确测量(微秒级就好了)。
编辑:这完全是我的错。 @Qeole 和@tuilagio 完全正确。我在获取时间戳指针的用户 space 代码中犯了一个指针运算错误。
您可以使用 clock_gettime()
:
static unsigned long get_nsecs(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000000000UL + ts.tv_nsec;
}
并用 unsigned long now = get_nsecs();
或 uint64_t now = get_nsecs();
调用它
这个 return 纳秒分辨率的时间戳。
来源:https://github.com/torvalds/linux/blob/master/samples/bpf/xdpsock_user.c#L114
这可能不是执行此操作的规范方法,但至少它很有趣:我们可以检索内核时间戳...从 BPF 本身!
BPF 子系统具有“test-运行”功能,允许使用用户提供的数据测试程序的某些类型,运行 由 bpf()
系统调用触发.这是一个这样做的示例应用程序:
- 它加载一个 BPF 程序(XDP,但类型并不重要)并获得一个 FD。
- 它重用 FD 来触发那个 BPF 程序的“test-运行”。
- 当它运行s时,程序调用
bpf_ktime_get_ns()
,将值复制到数据输出缓冲区(data_out
),我们只需要读取它来获取时间戳.
#define _GNU_SOURCE
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <linux/bpf.h>
int main(__attribute__((unused))int argc,
__attribute__((unused))char **argv)
{
union bpf_attr load_attr = { }, run_attr = { };
const struct bpf_insn insns[] = {
/* w0 = 1 | r0 = XDP_DROP */
{ .code = 0xb4, .src_reg = 0, .dst_reg = 0, .off = 0, .imm = 1, },
/* r2 = *(u32 *)(r1 + 4) | r2 = ctx->data_end */
{ .code = 0x61, .src_reg = 1, .dst_reg = 2, .off = 4, .imm = 0, },
/* r6 = *(u32 *)(r1 + 0) | r6 = ctx->data */
{ .code = 0x61, .src_reg = 1, .dst_reg = 6, .off = 0, .imm = 0, },
/* r1 = r6 | r1 = ctx->data */
{ .code = 0xbf, .src_reg = 6, .dst_reg = 1, .off = 0, .imm = 0, },
/* r1 += 8 | r1 += sizeof(uint64_t) */
{ .code = 0x07, .src_reg = 0, .dst_reg = 1, .off = 0, .imm = 8, },
/* if r1 > r2 goto +3 | if (data + 8 > data_end) return */
{ .code = 0x2d, .src_reg = 2, .dst_reg = 1, .off = 3, .imm = 0, },
/* call bpf_ktime_get_ns() */
{ .code = 0x85, .src_reg = 0, .dst_reg = 0, .off = 0, .imm = BPF_FUNC_ktime_get_ns, },
/* *(u64 *)(r6 + 0) = r0 | *(ctx->data) = bpf_ktime_get_ns() */
{ .code = 0x7b, .src_reg = 0, .dst_reg = 6, .off = 0, .imm = 0, },
/* w0 = 2 | r0 = XDP_PASS */
{ .code = 0xb4, .src_reg = 0, .dst_reg = 0, .off = 0, .imm = 2, },
/* exit | return r0 */
{ .code = 0x95, .src_reg = 0, .dst_reg = 0, .off = 0, .imm = 0, },
};
const char license[] = "GPL"; /* required for bpf_ktime_get_ns() */
/*
* Data buffers data_in/data_out must be at least the minimal size for
* an Ethernet frame: 14 header bytes
*/
const uint8_t data_out[14];
const uint8_t data_in[14];
int fd, res;
/* Load program */
load_attr.prog_type = BPF_PROG_TYPE_XDP;
load_attr.insn_cnt = sizeof(insns) / sizeof(insns[0]);
load_attr.insns = (uint64_t)insns;
load_attr.license = (uint64_t)license;
fd = syscall(__NR_bpf, BPF_PROG_LOAD, &load_attr, sizeof(load_attr));
if (fd < 0) {
fprintf(stderr, "failed to load BPF program: %s\n",
strerror(errno));
return EXIT_FAILURE;
}
/* Run program */
run_attr.test.prog_fd = fd;
run_attr.test.data_size_in = sizeof(data_in);
run_attr.test.data_size_out = sizeof(data_out);
run_attr.test.data_in = (uint64_t)data_in;
run_attr.test.data_out = (uint64_t)data_out;
res = syscall(__NR_bpf, BPF_PROG_TEST_RUN, &run_attr, sizeof(run_attr));
if (res) {
fprintf(stderr, "failed to run BPF program: %s\n",
strerror(errno));
close(fd);
return EXIT_FAILURE;
}
/* Extract result */
fprintf(stdout, "%lu\n", (uint64_t)run_attr.test.data_out);
close(fd);
return EXIT_SUCCESS;
}
请注意,我们还可以从程序的 return 值 (run_attr.test.retval
) 中提取数据,但这是一个 32 位整数,因此您无法获得完整的时间戳。这可用于检索例如只有该时间戳的秒数,右移 r0 >>= 32
,以避免进行 data
/data_end
长度检查并复制到 data_out
。并不是说它应该在性能上有很大变化。
运行 整个应用程序 (加载 + 运行) 显然会比后续的 运行 花费更长的时间,因为加载程序时在内核中完成了验证步骤。
附录: BPF 程序由以下代码生成:
#include <linux/bpf.h>
static unsigned long long (*bpf_ktime_get_ns)(void) =
(void *)BPF_FUNC_ktime_get_ns;
int xdp(struct xdp_md *ctx)
{
void *data_end = (void *) (long) ctx->data_end;
void *data = (void *) (long) ctx->data;
if (data + sizeof(unsigned long long) > data_end)
return XDP_DROP;
*(unsigned long long *)data = bpf_ktime_get_ns();
return XDP_PASS;
}
我想在我的 XDP 程序中收到的数据包中插入一个时间戳。我知道如何获取时间戳的唯一方法是调用 bpf_ktime_get_ns
.
但是创建可比时间戳的 user-space 等效函数是什么?据我所知,ktime_get_ns
returns 自系统启动以来的时间(以纳秒为单位)。有
$ uptime
11:45:35 up 2 days, 3:15, 3 users, load average: 0.19, 0.29, 0.27
但这只是 returns 自系统启动以来的时间 秒 。所以这里不可能进行精确测量(微秒级就好了)。
编辑:这完全是我的错。 @Qeole 和@tuilagio 完全正确。我在获取时间戳指针的用户 space 代码中犯了一个指针运算错误。
您可以使用 clock_gettime()
:
static unsigned long get_nsecs(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000000000UL + ts.tv_nsec;
}
并用 unsigned long now = get_nsecs();
或 uint64_t now = get_nsecs();
这个 return 纳秒分辨率的时间戳。
来源:https://github.com/torvalds/linux/blob/master/samples/bpf/xdpsock_user.c#L114
这可能不是执行此操作的规范方法,但至少它很有趣:我们可以检索内核时间戳...从 BPF 本身!
BPF 子系统具有“test-运行”功能,允许使用用户提供的数据测试程序的某些类型,运行 由 bpf()
系统调用触发.这是一个这样做的示例应用程序:
- 它加载一个 BPF 程序(XDP,但类型并不重要)并获得一个 FD。
- 它重用 FD 来触发那个 BPF 程序的“test-运行”。
- 当它运行s时,程序调用
bpf_ktime_get_ns()
,将值复制到数据输出缓冲区(data_out
),我们只需要读取它来获取时间戳.
#define _GNU_SOURCE
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <linux/bpf.h>
int main(__attribute__((unused))int argc,
__attribute__((unused))char **argv)
{
union bpf_attr load_attr = { }, run_attr = { };
const struct bpf_insn insns[] = {
/* w0 = 1 | r0 = XDP_DROP */
{ .code = 0xb4, .src_reg = 0, .dst_reg = 0, .off = 0, .imm = 1, },
/* r2 = *(u32 *)(r1 + 4) | r2 = ctx->data_end */
{ .code = 0x61, .src_reg = 1, .dst_reg = 2, .off = 4, .imm = 0, },
/* r6 = *(u32 *)(r1 + 0) | r6 = ctx->data */
{ .code = 0x61, .src_reg = 1, .dst_reg = 6, .off = 0, .imm = 0, },
/* r1 = r6 | r1 = ctx->data */
{ .code = 0xbf, .src_reg = 6, .dst_reg = 1, .off = 0, .imm = 0, },
/* r1 += 8 | r1 += sizeof(uint64_t) */
{ .code = 0x07, .src_reg = 0, .dst_reg = 1, .off = 0, .imm = 8, },
/* if r1 > r2 goto +3 | if (data + 8 > data_end) return */
{ .code = 0x2d, .src_reg = 2, .dst_reg = 1, .off = 3, .imm = 0, },
/* call bpf_ktime_get_ns() */
{ .code = 0x85, .src_reg = 0, .dst_reg = 0, .off = 0, .imm = BPF_FUNC_ktime_get_ns, },
/* *(u64 *)(r6 + 0) = r0 | *(ctx->data) = bpf_ktime_get_ns() */
{ .code = 0x7b, .src_reg = 0, .dst_reg = 6, .off = 0, .imm = 0, },
/* w0 = 2 | r0 = XDP_PASS */
{ .code = 0xb4, .src_reg = 0, .dst_reg = 0, .off = 0, .imm = 2, },
/* exit | return r0 */
{ .code = 0x95, .src_reg = 0, .dst_reg = 0, .off = 0, .imm = 0, },
};
const char license[] = "GPL"; /* required for bpf_ktime_get_ns() */
/*
* Data buffers data_in/data_out must be at least the minimal size for
* an Ethernet frame: 14 header bytes
*/
const uint8_t data_out[14];
const uint8_t data_in[14];
int fd, res;
/* Load program */
load_attr.prog_type = BPF_PROG_TYPE_XDP;
load_attr.insn_cnt = sizeof(insns) / sizeof(insns[0]);
load_attr.insns = (uint64_t)insns;
load_attr.license = (uint64_t)license;
fd = syscall(__NR_bpf, BPF_PROG_LOAD, &load_attr, sizeof(load_attr));
if (fd < 0) {
fprintf(stderr, "failed to load BPF program: %s\n",
strerror(errno));
return EXIT_FAILURE;
}
/* Run program */
run_attr.test.prog_fd = fd;
run_attr.test.data_size_in = sizeof(data_in);
run_attr.test.data_size_out = sizeof(data_out);
run_attr.test.data_in = (uint64_t)data_in;
run_attr.test.data_out = (uint64_t)data_out;
res = syscall(__NR_bpf, BPF_PROG_TEST_RUN, &run_attr, sizeof(run_attr));
if (res) {
fprintf(stderr, "failed to run BPF program: %s\n",
strerror(errno));
close(fd);
return EXIT_FAILURE;
}
/* Extract result */
fprintf(stdout, "%lu\n", (uint64_t)run_attr.test.data_out);
close(fd);
return EXIT_SUCCESS;
}
请注意,我们还可以从程序的 return 值 (run_attr.test.retval
) 中提取数据,但这是一个 32 位整数,因此您无法获得完整的时间戳。这可用于检索例如只有该时间戳的秒数,右移 r0 >>= 32
,以避免进行 data
/data_end
长度检查并复制到 data_out
。并不是说它应该在性能上有很大变化。
运行 整个应用程序 (加载 + 运行) 显然会比后续的 运行 花费更长的时间,因为加载程序时在内核中完成了验证步骤。
附录: BPF 程序由以下代码生成:
#include <linux/bpf.h>
static unsigned long long (*bpf_ktime_get_ns)(void) =
(void *)BPF_FUNC_ktime_get_ns;
int xdp(struct xdp_md *ctx)
{
void *data_end = (void *) (long) ctx->data_end;
void *data = (void *) (long) ctx->data;
if (data + sizeof(unsigned long long) > data_end)
return XDP_DROP;
*(unsigned long long *)data = bpf_ktime_get_ns();
return XDP_PASS;
}