bpf map映射简介

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->
         
		
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值