寄存器: CPU不断读写内存是并不很快的, 这个速度好像还取决与对应的总线长度, 因此我们需要一个中间变量来帮助我们来快速访问对应的内存, 这个时候我们就需要使用到栈。
所以我们的寄存器, 通常分为下面几种:
- 数据寄存器, 一般用于存储数据
- 地址寄存器, 用于存储地址, 常见的有段指针
在eBPF中常见的寄存器有下面这几种:
- R0, 这个寄存器一般用于存储对应函数返回值
- R1-R5, 这几个寄存器用于存储函数的参数值
- R10, 这个寄存器的作用就是用来存放栈指针
一般两个程序如果想要交换信息, 一般可以通过两种方式:
- 一种是通过socket通信
- 第二种是通过共享内存
但是我们的eBPF有一个地方需要注意那就是, 我们的程序是会加载到内核中, 但是我们是在用户层, 所以需要一个媒介, 这个媒介就是BPF MAP。
这个BPF MAP提供了大块的存储在用户空间和内核空间, 这个map可以映射多种数据结构, 比如hashmap, 下面我为你演示了一下这个hashmap
BPF_HASH(counter_table);
int hello(void* ctx) {
u64 uid;
u64 counter = 0;
u64* p;
uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
p = counter_table.lookup( &uid );
if ( p != 0 ) {
counter = *p;
}
counter++;
counter_table.update(&uid, &counter);
return 0;
}
上面这段程序我使用了BPF_HASH()创建了一个hashmap, 这个map的类型是u64到u64上的, 但是这种东西有一个特点就是无法持续传输新的数据从我们的ebpf程序到用户, 这对持续记录事件是很影响的。
但是这种数据结构是比较适合数据的查找的, 比如内核向数据结构写一个数据, 然后我们可以通过应用程序取出对应的值
BPF_MAP_TYPE_PERF_EVENT_ARRAY
,这个map函数是用来创建对应的ringbuffer的, 但是这个函数创建的东西是有限制的, 可能会导致内存的浪费, 事件的顺序无法得到一定的保证。
BPF_PERF_OUTPUT(output);
struct data_t {
int pid;
int uid;
char command[16];
char message[12];
};
int hello(void* ctx) {
struct data_t data = {};
char message[12] = "Hello, World";
data.pid = bpf_get_current_pid_tgid() >> 32;
data.uid = buf_get_curretn_uid_gid() & 0xFFFFFFFF;
bpf_get_current_comm(&data.command, sizeof(data.command));
bpf_probe_read_kernal(&data.message, sizeof(data.message), ,message);
output.perf_submit(ctx, &data, sizeof(data));
return 0;
}