bpf map映射简介
bpf程序运行在内核,而且为了保证bpf程序运行的安全性,bpf虚拟机限制了系统调用,如果bpf程序需要和用户空间程序进程通信就需要bpf映射,bpf提供了如hash、stack、array等映射,在bpf程序中可以根据需求直接使用。
bcc bpf map映射分析
bcc中的BPF可以使用bcc的宏定义定义,当bcc加载bpf程序时再转换成section段定义进行编译。
bcc的BPF映射定义在src/cc/export/helpers.h文件,bcc编译bpf代码时替换bpf程序中的宏定义为满足bpf映射的格式再进行编译。
bcc/tools/filelife.py示例:
# define BPF program
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <linux/fs.h>
#include <linux/sched.h>
struct data_t {
u32 pid;
u64 delta;
char comm[TASK_COMM_LEN];
char fname[DNAME_INLINE_LEN];
};
BPF_HASH(birth, struct dentry *);
BPF_PERF_OUTPUT(events);
// trace file creation time
int trace_create(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry)
{
u32 pid = bpf_get_current_pid_tgid() >> 32;
FILTER
u64 ts = bpf_ktime_get_ns();
birth.update(&dentry, &ts);
return 0;
};
// trace file deletion and output details
int trace_unlink(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry)
{
struct data_t data = {};
u32 pid = bpf_get_current_pid_tgid() >> 32;
FILTER
u64 *tsp, delta;
tsp = birth.lookup(&dentry);
if (tsp == 0) {
return 0; // missed create
}
delta = (bpf_ktime_get_ns() - *tsp) / 1000000;
birth.delete(&dentry);
struct qstr d_name = dentry->d_name;
if (d_name.len == 0)
return 0;
if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0) {
data.pid = pid;
data.delta = delta;
bpf_probe_read_kernel(&data.fname, sizeof(data.fname), d_name.name);
}
events.perf_submit(ctx, &data, sizeof(data));
return 0;
}
"""
filelife.py示例提供了BPF_HASH映射的增删改查功能,birth.update更新映射元素、birth.delete删除映射元素、birth.lookup查找映射元素。
BPF_HASH是定义在c++代码中的宏定义,展开如下:
#define BPF_HASH1(_name) \
BPF_TABLE("hash", u64, u64, _name, 10240)
#define BPF_HASH2(_name, _key_type) \
BPF_TABLE("hash", _key_type, u64, _name, 10240)
#define BPF_HASH3(_name, _key_type, _leaf_type) \
BPF_TABLE("hash", _key_type, _leaf_type, _name, 10240)
#define BPF_HASH4(_name, _key_type, _leaf_type, _size) \
BPF_TABLE("hash", _key_type, _leaf_type, _name, _size)
// helper for default-variable macro function
#define BPF_HASHX(_1, _2, _3, _4, NAME, ...) NAME
// Define a hash function, some arguments optional
// BPF_HASH(name, key_type=u64, leaf_type=u64, size=10240)
#define BPF_HASH(...) \
BPF_HASHX(__VA_ARGS__, BPF_HASH4, BPF_HASH3, BPF_HASH2, BPF_HASH1)(__VA_ARGS__)
BPF_HASH宏定义中的可变参替换到BPF_HASHX宏定义的开头,后面再使用宏定义方式动态生成不同参数的HASH_TABLE宏定义,展开如下所示:
#define BPF_HASHX(_1, _2, _3, _4, NAME, ...) NAME 返回第五个参数名字
BPF_HASH(birth, struct dentry *);
展开:
BPF_HASHX(birth, struct dentry *, BPF_HASH4, BPF_HASH3, BPF_HASH2, BPF_HASH1)(birth, struct dentry *)
BPF_HASH2(birth, struct dentry *)
展开:
BPF_TABLE("hash", struct dentry *, u64, birth, 10240)
BCC巧妙使用了C/C++宏定义计算可变参参数个数的方式动态生产了不同参数的宏定义。
最终宏定义替换成具备默认函数指针的结构体,section段指向“maps/”,这样就和自己编码bpf程序定义map的结构一样了。
如下的update、insert、delete、call等方法就是文章开始提到的增删改查函数指针,功能由内核实现。
#define BPF_TABLE(_table_type, _key_type, _leaf_type, _name, _max_entries) \
BPF_F_TABLE(_table_type, _key_type, _leaf_type, _name, _max_entries, 0)
// Changes to the macro require changes in BFrontendAction classes
#define BPF_F_TABLE(_table_type, _key_type, _leaf_type, _name, _max_entries, _flags) \
struct _name##_table_t { \
_key_type key; \
_leaf_type leaf; \
_leaf_type * (*lookup) (_key_type *); \
_leaf_type * (*lookup_or_init) (_key_type *, _leaf_type *); \
_leaf_type * (*lookup_or_try_init) (_key_type *, _leaf_type *); \
int (*update) (_key_type *, _leaf_type *); \
int (*insert) (_key_type *, _leaf_type *); \
int (*delete) (_key_type *); \
void (*call) (void *, int index); \
void (*increment) (_key_type, ...); \
int (*get_stackid) (void *, u64); \
u32 max_entries; \
int flags; \
}; \
__attribute__((section("maps/" _table_type))) \
struct _name##_table_t _name = { .flags = (_flags), .max_entries = (_max_entries) }; \
BPF_ANNOTATE_KV_PAIR(_name, _key_type, _leaf_type)
以上为BPF_HASH宏定义展开后的情况,下面是HASH_STACK宏定义bpf的栈映射展开后的情况:
// Changes to the macro require changes in BFrontendAction classes
#define BPF_QUEUESTACK(_table_type, _name, _leaf_type, _max_entries, _flags) \
struct _name##_table_t { \
_leaf_type leaf; \
int * (*peek) (_leaf_type *); \
int * (*pop) (_leaf_type *); \
int * (*push) (_leaf_type *, u64); \
u32 max_entries; \
int flags; \
}; \
__attribute__((section("maps/" _table_type))) \
struct _name##_table_t _name = { .flags = (_flags), .max_entries = (_max_entries) }; \
BPF_ANNOTATE_KV_PAIR_QUEUESTACK(_name, _leaf_type)
bcc创建映射
bpf代码中创建映射后作为代码段编译在bpf的obj程序中,当bcc加载bpf程序时会读取map段数据进行创建。
首先bcc会编译bpf代码,然后把bpf程序的section数据生成btf格式数据,并在用户空间根据使用malloc分配一块内存空间,使用bpf BPF_BTF_LOAD系统调用加载进内核,最后在通过load_maps流程创建bpf map。
编译btf流程:
BPFModule::load_string->
finalize->
load_btf->
btf->load->
btf__new->
malloc
btf__load->
bpf_load_btf->
sys_bpf(BPF_BTF_LOAD, &attr, sizeof(attr));
load_maps->
create_maps->
bcc_create_map_xattr->
bpf_create_map_xattr->
(获取btf_loadmalloc的fd,然后调用bpf系统调用进行map创建)
attr.btf_fd = create_attr->btf_fd;
sys_bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
内核bpf map创建流程
kernel收到系统调用后进入创建map的流程,首先根据参数获取map类型(如hash、array等),然后使用对应map创建map,获取btf数据,根据btf数据校验map信息,内核分配map id,绑定map id到对应的map。
SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, size)->
map_create->
find_and_alloc_map->
map = ops->map_alloc(attr);
btf_get_by_fd->
map_check_btf->
bpf_map_new_fd->