宏是 C 语言中最强大的语言特性,能够用来简化 eBPF 的 C 代码;毕竟 eBPF 的 C 代码是一种语法、语义都受限的 C 代码,不能像普通 C 代码那样“肆意妄为”。
写法一:带有返回值的多行宏
常见的宏定义是常量定义,如 #define VXLAN_PORT 4789
。可是如果在宏定义中包含有 if
条件判断,该如何编写该宏定义呢?不使用函数来实现,是因为函数需要带上类型,而宏定义则不需要参数的类型。
1
2
3
4
5
6
7
8
9
10
|
#define GET_TOS(sip, pod) \
({ \
__u8 tos; \
if (USE_TOS) \
tos = POD_TOS; \
else \
tos = __get_pod_tos(sip, pod); \
\
tos; \
})
|
如上代码片段,使用 ({})
包裹多行代码,且在第一行声明返回值、在最后一行提供返回值。
参考 BPF_CORE_READ
的宏定义:
1
2
3
4
5
|
#define BPF_CORE_READ(src, a, ...) ({ \
___type((src), a, ##__VA_ARGS__) __r; \
BPF_CORE_READ_INTO(&__r, (src), a, ##__VA_ARGS__); \
__r; \
})
|
写法二:多行宏的推荐写法
对于多行宏,常见写法如下:
1
2
3
4
5
|
#define __load_bytes(dst, size) \
bpf_skb_load_bytes_relative(skb, var_off, ev->buff + cpy_off, size, BPF_HDR_START_MAC); \
cpy_off += size; \
ev->total_len = cpy_off; \
var_off += size;
|
直接写成多行。可是如果需要在宏里定义一个临时变量,这种写法就行不通了。此时,可以采取下面这种写法:
1
2
3
4
5
6
7
8
9
10
11
12
|
#define print_ip_version(iph) \
do { \
__u8 b; \
bpf_probe_read(&b, 1, iph); \
b >>= 4; \
if (b == 4) \
bpf_printk("IPv4\n"); \
else if (b == 6) \
bpf_printk("IPv6\n"); \
else \
bpf_printk("Unkown IP version\n"); \
} while (0)
|
使用 do {} while(0)
包裹着多行代码。此时,do-while
里的代码块只会执行一遍。而在使用的时候,像函数调用一样直接使用就可以了,print_ip_version(iph);
。
小结
在 Linux 内核的源代码中,写法二更加常见。而写法一,则是从其它 eBPF 项目中学习到的。
多看源代码,多从源代码中学习。