2

我想在我的 XDP 程序中收到的数据包中插入一个时间戳。我知道如何获取时间戳的唯一方法是调用bpf_ktime_get_ns.

但是创建可比较时间戳的用户空间等效函数是什么?据我所知,ktime_get_ns返回自系统启动以来的时间(以纳秒为单位)。有

$ uptime
 11:45:35 up 2 days,  3:15,  3 users,  load average: 0.19, 0.29, 0.27

但这仅返回自系统启动以来的时间(以为单位)。所以这里不可能进行精确的测量(微秒级会很好)。

编辑:这纯粹是我的错。@Qeole 和@tuilagio 是完全正确的。在获取时间戳指针的用户空间代码中,我在指针算术中犯了一个错误。

4

2 回答 2

3

这可能不是执行此操作的规范方式,但至少它很有趣:我们可以从 BPF 本身检索内核时间戳!

BPF 子系统有一个“测试运行”特性,允许使用用户提供的数据测试某些类型的程序,运行由bpf()系统调用触发。这是一个这样做的示例应用程序:

  1. 它加载一个 BPF 程序(XDP,但类型无关紧要)并获得一个 FD。
  2. 它重用 FD 来触发该 BPF 程序的“试运行”。
  3. 当它运行时,程序调用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;
}

请注意,我们也可以从程序 ( ) 的返回值中提取数据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;
}
于 2020-04-03T09:43:38.440 回答
2

您可以使用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();并用or调用它uint64_t now = get_nsecs();

此返回时间戳以 nsec 为单位。

来源:https ://github.com/torvalds/linux/blob/master/samples/bpf/xdpsock_user.c#L114

于 2020-04-01T17:39:58.357 回答