XDP 上的线程安全操作

Thread safe operations on XDP

我能够从文档中确认,如果在 HASH_MAPs 上完成,bpf_map_update_elem 是一个原子操作。来源(https://man7.org/linux/man-pages/man2/bpf.2.html)。 [引用:map_update_elem() 以原子方式替换现有元素]

我的问题是2折

  1. 如果元素不存在怎么办,map_update_elem还是原子的吗?

  2. 用户 space 程序的 XDP 操作 bpf_map_delete_elem 线程安全吗?

地图是 HASH_MAP.

原子操作、竞争条件和线程安全在 eBPF 中有点复杂,所以我会做一个宽泛的回答,因为很难从你的问题中判断你的目标是什么。

是的,通过系统调用的 bpf_map_update_elem 命令和辅助函数都会更新映射 'atmomically',在这种情况下,这意味着如果我们从值 'A' 转到值 'B' 程序总是看到 'A' 或 'B' 而不是两者的某种组合(例如 'B' 的第一个字节和 'A' 的最后一个字节)。这适用于所有地图类型。这适用于所有地图修改系统调用命令(包括 bpf_map_delete_elem)。

然而,这不会使竞争条件变得不可能,因为地图的值可能在 map_lookup_elem 和您更新它的那一刻之间发生了变化。

同样要记住的是 map_lookup_elem 系统调用命令(用户空间)与辅助函数(内核空间)的工作方式不同。系统调用将始终 return 一份不可变的数据副本。但是辅助函数将 return 指向内核内存中存储映射值的位置的指针,您可以通过这种方式直接更新映射值,而无需使用 map_update_elem 辅助函数。这就是为什么您经常看到像这样使用哈希映射的原因:

value = bpf_map_lookup_elem(&hash_map, &key);
if (value) {
    __sync_fetch_and_add(&value->packets, 1);
    __sync_fetch_and_add(&value->bytes, skb->len);
} else {
    struct pair val = {1, skb->len};

    bpf_map_update_elem(&hash_map, &key, &val, BPF_ANY);
}

请注意,在这个example中,__sync_fetch_and_add用于更新部分地图值。我们需要这样做,因为像 value->packets++;value->packets += 1 那样更新它会导致竞争条件。 __sync_fetch_and_add 发出一个原子 CPU 指令,在这种情况下,它在一条指令中获取、添加和写回所有内容。

此外,在这个例子中,两个结构字段被自动更新,但不是一起更新,仍然有可能 packets 已经增加但 bytes 还没有。如果你想避免这种情况,你需要使用自旋锁(使用 bpf_spin_lockbpf_spin_unlock helpers)。

完全回避这个问题的另一种方法是使用地图的 _PER_CPU 变体,其中您 trade-off congestion/speed 和内存使用。