网站建设推销话术案例,网站优化的关键词,软件营销网站,计算机作业网页设计代码目录
引言
什么是BPF
历史
组成
执行机制
BPF 和ebpf 的关系
BCC、bpftrace、IO Visor
BCC 项目的quick start
execsnoop
biolatency
动态插桩#xff1a;kprobes和uprobes
概念
缺点
静态插桩#xff1a;tracepoint 和USDT
概念
缺点
推荐的方案
初识bpft…目录
引言
什么是BPF
历史
组成
执行机制
BPF 和ebpf 的关系
BCC、bpftrace、IO Visor
BCC 项目的quick start
execsnoop
biolatency
动态插桩kprobes和uprobes
概念
缺点
静态插桩tracepoint 和USDT
概念
缺点
推荐的方案
初识bpftrace : 跟踪openat
技术背景
图解BPF
BPF辅助函数
bpf_probe_read()
为什么bpf_probe_read 要禁止缺页中断
bpf_probe_read是如何禁止缺页中断的
样例
BPF 系统调用命令
使用strace 分析execsnoop
BPF程序类型
BPF 映射表类型
BPF的并发控制
BPF sysfs 接口
BPF 类型格式
BPF CO-RE
BPF 的局限性
调用栈回溯
基于帧指针的调用栈回溯
基于调试信息来做debug
最后分支记录
ORC
符号
火焰图
事件源
Kprobes
uprobes
跟踪点 tracepoint
USDT
性能监控计数器 引言
什么是BPF
历史
是 Berkeley Packet Filter 的缩写诞生于1992 目的是提高网络包的过滤工具的性能。
2014年进入了Linux 内核的主线。
从使用的方式上来说比较像是JavaScript。
组成
指令集、存储对象、辅助函数等几部分组成。
执行机制
一般有两种执行机制
解释器一个讲BPF指令动态转化成本地化指令的即时JIT 编译器
执行之前要通过验证器的安全性检查可以保证BPF程序本身不会奔溃 BPF 和ebpf 的关系
现在的ebpf 为了和之前保持一致还是继续称呼为BPF。 BCC、bpftrace、IO Visor
直接通过BPF 指令编写BPF程序是比较繁琐的因此有了高级语言去支持。 BCCBPF编译器集合BPF最早用于开发BPF trace 程序提供了一个C语言的环境也提供了lua、python环境来实现用户端接口是libbcc和libbpf库的前身这两个库提供了使用BPF 程序对事件进行观测的库函数。
BCC 函数库提供了70多个tool。 bpftrace 是新出现的前端提供了专门用于创建BPF工具的高级语言支持。bpftrace 也是基于libbcc和libpbf库进行构建的。
bpftrace 在编写功能强大的单行程序短小的脚本比较在行BCC 主要开发比较复杂的大型后端进程。 BCC 和bpftrace 不属于linux 内核属于GITHUB上的一个 IO ViSor 的linux 基金会。 BCC 项目的quick start
execsnoop
来自bcc 项目通过跟踪execve 系统调用来工作。
sudo execsnoop-bpfcc 作用
可以使用这个工具来检查业务负载就是说可以看到进程是不是按照自己的想法在一定的周期下被创建出来。 biolatency
概念
绘制块设备的延迟直方图disk IO latency sudo biolatency-bpfcc
在虚拟机环境下这个命令会在内核版本5.19.0-35-generic 下执行失败。
动态插桩kprobes和uprobes
概念
在生产环境中正在运行的程序的任意指令位置插入观测点
缺点
随着版本的更换被插桩的函数可能会重新命名或者直接被去掉这样会有稳定性的问题BPF工具可能会直接无法工作。 静态插桩tracepoint 和USDT
概念
本质上就是为了解决上面动态插桩中接口稳定性的问题直接把稳定的事件名硬编码到代码中直接由开发者进行维护。
USDTuser level statically defined tracing就是描述这个技术的。
缺点
硬编码会带来额外的维护成本 推荐的方案
首先使用静态的跟踪技术tracpoint 或者USDT如果不够用再使用动态插桩技术。 初识bpftrace : 跟踪openat
sudo bpftrace -e tracepoint:syscalls:sys_enter_openat {printf(%s %s \n,comm,str(args-filename));} 这里有一些小的tips
openat 在linux 中被调用的次数远远超过openbpftrace 必须要使用root 权限bpftrace 一般比较simple支持一些比较小的命令工具但是BCC tool功能比较强大技术背景
图解BPF BPF辅助函数
Map操作函数BPF_MAP_LOOKUP_ELEM、BPF_MAP_UPDATE_ELEM、BPF_MAP_DELETE_ELEM等内存操作函数BPF_MEMCPY、BPF_MEMCPY_STR、BPF_MEMSET等网络操作函数BPF_SOCK_OPS_TCP_SOCK等时间操作函数BPF_KTIME_GET_NS等系统调用操作函数BPF_TRACE_PRINTK、BPF_GET_CURRENT_PID_TID等数学计算函数BPF_ADD、BPF_SUB、BPF_MUL、BPF_DIV等其他函数BPF_DEBUG、BPF_EXIT等bpf_probe_read()
BPF中的内存访问仅仅限于 BPF寄存器和栈空间以及通过辅助函数访问BPF映射表如果访问其他内存比如说除了BPF之外的内存就需要使用bpf_probe_read()。
这个函数会进程安全性的检查禁止出现缺页中断以保证在probe 上下文中不会发生缺页中断否则可能会引发内核问题。
还有其他的辅助函数 bpf_probe_read_kernel、bpf_probe_read_user() 为什么bpf_probe_read 要禁止缺页中断 在Linux内核中BPF程序可以在内核空间中访问用户空间内存。当BPF程序访问的用户空间地址空间中的页面不在物理内存中时会触发缺页中断然后内核会将对应页从磁盘中读入内存完成物理内存的分配操作这个过程是比较耗时的。
但是在某些情况下我们可能并不需要立即读入缺页中断所对应的页面比如在处理高速网络数据包时不能承受太大的时延。因此我们可以通过禁止缺页中断来避免耗时的物理内存分配提高处理性能。 bpf_probe_read是如何禁止缺页中断的
需要设置当前线程的脱离缺页中断标志位
/* 禁用缺页中断 */
void disable_page_fault(void)
{preempt_disable();current-flags | PF_NOFREEZE;current-mm-def_flags | VM_FAULT_NOPAGE;
}
样例
使用bpf_probe_read 函数读取skb 中UDP 数据包
读取UDP数据包的过程需要先读取IP头部然后再根据IP协议类型字段确定上层协议为UDP接着读取UDP头部然后再读取数据载荷。以下是一个使用bpf_probe_read()函数读取UDP数据包的示例代码
#include linux/bpf.h
#include linux/if_ether.h
#include linux/ip.h
#include linux/udp.hint mybpf_prog(struct __sk_buff *skb)
{void *data (void *)(long)skb-data;void *data_end (void *)(long)skb-data_end;struct ethhdr *eth data;if (eth 1 data_end)return 0;// 读取IP头struct iphdr iph;if (bpf_probe_read(iph, sizeof(iph), (void *)(eth 1)) ! 0)return 0;if (iph.protocol IPPROTO_UDP) {// 读取UDP头struct udphdr uh;if (bpf_probe_read(uh, sizeof(uh), (void *)((unsigned char *)iph (iph.ihl * 4))) ! 0)return 0;// 计算数据包总长度unsigned int len ntohs(iph.tot_len) - (iph.ihl * 4) - sizeof(uh);if (len 0) // 数据包长度错误return 0;// 读取数据载荷unsigned char payload[len];if (bpf_probe_read(payload, len, (void *)((unsigned char *)uh sizeof(uh))) ! 0)return 0;// 对读取的数据载荷进行处理...return 1;}return 0;
}
BPF 系统调用命令 使用strace 分析execsnoop
sudo strace -ebpf execsnoop-bpfcc bpf(BPF_BTF_LOAD, {btf\237\353\1\0\30\0\0\0\0\0\0\0\274\4\0\0\274\4\0\0H\17\0\0\0\0\0\0\0\0\0\2..., btf_log_bufNULL, btf_size5148, btf_log_size0, btf_log_level0}, 28) 3
bpf(BPF_MAP_CREATE, {map_typeBPF_MAP_TYPE_PERF_EVENT_ARRAY, key_size4, value_size4, max_entries128, map_flags0, inner_map_fd0, map_nameevents, map_ifindex0, btf_fd0, btf_key_type_id0, btf_value_type_id0, btf_vmlinux_value_type_id0, map_extra0}, 72) 4
bpf(BPF_PROG_LOAD, {prog_typeBPF_PROG_TYPE_KPROBE, insn_cnt510, insns0x7ff36007d000, licenseGPL, log_level0, log_size0, log_bufNULL, kern_versionKERNEL_VERSION(5, 19, 17), prog_flags0, prog_namesyscall__execve, prog_ifindex0, expected_attach_typeBPF_CGROUP_INET_INGRESS, prog_btf_fd3, func_info_rec_size8, func_info0x5645a446e430, func_info_cnt1, line_info_rec_size16, line_info0x5645a4a88f20, line_info_cnt252, attach_btf_id0, attach_prog_fd0, fd_arrayNULL}, 144) 5
bpf(BPF_PROG_LOAD, {prog_typeBPF_PROG_TYPE_KPROBE, insn_cnt82, insns0x7ff36021b7d0, licenseGPL, log_level0, log_size0, log_bufNULL, kern_versionKERNEL_VERSION(5, 19, 17), prog_flags0, prog_namedo_ret_sys_exec, prog_ifindex0, expected_attach_typeBPF_CGROUP_INET_INGRESS, prog_btf_fd3, func_info_rec_size8, func_info0x5645a446e430, func_info_cnt1, line_info_rec_size16, line_info0x5645a348d760, line_info_cnt28, attach_btf_id0, attach_prog_fd0, fd_arrayNULL}, 144) 7
PCOMM PID PPID RET ARGS
bpf(BPF_MAP_UPDATE_ELEM, {map_fd4, key0x7ff35babc690, value0x7ff35babc590, flagsBPF_ANY}, 144) 0
bpf(BPF_MAP_UPDATE_ELEM, {map_fd4, key0x7ff35babc590, value0x7ff35babc690, flagsBPF_ANY}, 144) 0
^Cstrace: Process 4540 detachedPS 最好是可以避免直接使用strace 因为使用strace 本质上使用ptrace会严重的降低目标进程的执行速度性能可能会直接下降到原来的1%但是他的好处是可以支持bpf 系统调用的翻译比如说可以打印出BPF_PROG_LOAD BPF程序类型
不同的bpf程序类型定义了BPF 程序可以挂载的事件类型以及事件的参数主要用于trace 用途的BPF程序的类型如下 BPF 映射表类型 其中BPF_MAP_TYPE_PERF_EVENT_ARRAY 可以将内核中的捕捉到的信息传递到user execsnoop就使用了这个类型。 BPF的并发控制
BPF一直缺乏并发控制直到Linux 5.1添加了自旋锁 spin lock帮助程序尽管这些还不能用于跟踪程序。
通过跟踪并行线程可以并行地查找和更新BPF映射字段当一个线程从另一个线程覆盖更新时就会导致破坏。这也称为丢失更新问题lost update即并发读写重叠导致丢失更新。跟踪前端BCC和bpftrace在可能的情况下使用每cpu哈希和数组映射类型来避免这种损坏。这将为每个逻辑CPU创建实例(比如说使用BPF_MAP_TYPE_PERCPU_HASH这种带有PERCPU 的type) 这是使用了BPF_MAP_TYPE_PERCPU_HASH type 的方式 strace -febpf bpftrace -e k:vfs_read { count(); }
这是没有使用并发控制的方式
strace -febpf bpftrace -e k:vfs_read { ; }
Comparing the counts shows that the normal hash undercounted events by 0.01%.
对比下来会有0.01% 的误差。 BPF sysfs 接口
在Linux 4.4中BPF引入了通过虚拟文件系统公开BPF程序和映射的命令通常挂载在/sys/fs/ BPF上。这被称为“钉住”这可以有很多用途。它允许创建持久的BPF程序类似于守护进程并在加载它们的进程退出后继续运行。它还为用户级程序与正在运行的BPF程序进行交互提供了另一种方式:它们可以读写BPF映射。 BPF 类型格式
如果说我们缺少目标程序的源代码导致写一些BPF tool 比较困难那么这里有一种BTF 的技术可以解决这个问题。
但是BTF 技术还在开发过程中。 BPF CO-RE
BPF 的“一次编译到处运行的”Compile Once - Run Everywhere 项目旨在允许BPF程序一次性编译为BPF字节码保存然后在其他系统上分发和执行。这将避免在所有地方安装BPF编译器(LLVM和Clang)这对于空间受限的嵌入式Linux来说是一个挑战。它还可以避免在执行BPF可观察性工具时运行编译器所带来的运行时CPU和内存成本。 CO-RE 目前也在开发阶段。 BPF 的局限性
不可以随意的使用内核函数BPF 的栈的大小不可以超过512 MAX_BPF_STACK这个有解决方案使用映射存储空间。调用栈回溯
BPF 提供了存储调用栈信息的而专用映射表结构可以保存基于帧指针或基于ORC的调用栈回溯信息。
基于帧指针的调用栈回溯
这个技术主要基于一个前提函数调用栈帧链表的头部始终保存在某一个寄存器中RBP on x86_64并且这个函数调用的返回地址永远位于RBP 的值指向的位置加上一个固定的偏移量8。
这标志任何调试器都可以在中断程序执行之后通过读取RBP后遍历以RBP的值为头部的链表同时在固定偏移位置获取返回地址从而轻松的进行栈回溯。 PS 在gcc 编译器中默认是没有函数帧指针的将RBP作为普通的寄存器来使用的但是这个性能的提升其实没有很高所以建议开启这个默认行为。
-fno-omit-frame-pointer
基于调试信息来做debug
也就是在gcc 的后面加 -g -wall 的意思。
这其中包含了DWARF的ELF 的调试信息
在ELF文件中调试相关的文件段是 .eh_frame 和.debug_frame
缺点就是这样会让这个可执行文件非常大
libjvm.so 17M
libjvmd.so 222M
最后分支记录
也就是LBR是inter 的一个特别的技术被记录在硬件缓冲区中这个技术没有额外的开销。
但是支持记录的深度会存在限制。
BPF不支持LBR ORC
针对栈回溯需求专门设计了一种新的调试信息格式——Oops 回滚能力Oops rewind capabilityORC。相比于DWARF格式使用这种格式对于处理器的要求比较低ORC使用的也是ELF的文件段目前linux 内核以及给了一部分支持。
目前还没有开发用户态对ORC调用栈的支持。
在内核中基于ORC的调用栈回溯可以通过 perf_callchain_kernel 函数支持。 符号
调用栈信息目前在内核中以地址数据形式记录的这些地址可以通过用户态的程序翻译成符号比如函数的名字。
但是这部分工作目前还没有完成。
火焰图
火焰图是一种用于显示程序 CPU 使用状况的可视化工具。下面给出一些火焰图的基本用法和解读方法
坐标轴火焰图的 y 轴标识调用栈从上到下表示函数调用的深度。x 轴表示 CPU 时间可以是毫秒、秒或其他单位从左到右表示程序的运行时间。颜色火焰图的颜色表示函数调用在 CPU 时间或计数器空间上所占的相对值。宽度火焰图中的每个矩形宽度表示相关代码的 CPU 时间或者计数器测得值。工具例如Flamegraphperf等火焰图生成工具都支持启用各种设定如矩形排列顺序颜色方案等等。
根据以上信息接下来了解如何解读火焰图
开头与结尾火焰图的开始和结尾通常是程序的入口和退出点在火焰图中通常具有相当宽的板块和颜色较浅。宽度火焰图中宽度较大的矩形表示在程序执行过程中的资源消耗比较高其代码运行时间也相应较长。颜色火焰图中的颜色从浅绿色到深绿色选区浅绿颜色区域是程序当中相对花费时间的低开销地方深绿区域则追求更大的时间花费。重复调用在火焰图中相同函数的重复调用会显示相同的框框内的宽度指示它被调用的次数和执行时间的相对大小。堆栈信息火焰图中的每个函数调用的名称和调用堆栈信息也可用于鉴别和调整程序的性能问题。
事件源
Kprobes
kprobes 和kretprobes 的概念
kprobes 提供了针对内核的动态插桩支持不需要重启内核。
kretprobes可以用来对内核函数返回时进行插桩以获取返回值也可以用kprobes 和kretprobes 同时对一个函数进行插桩来获取一个内核函数调用的时间。
原理
注册断点Kprobes利用内核的动态内存分配技术在内核 web 服务器代码的关键位置注册断点。断点触发当内核执行到已注册断点的位置时则立即暂停执行并在此时开始执行 Kprobes 注册的处理程序。处理程序Kprobes 的处理程序可以是一个用户自定义的函数它可以在断点处挂钩处理内核函数调用并记录性能数据或更改内核状态以进行调试或分析。处理完成处理程序完成后Kprobes 将继续执行中断位置的剩余代码完成对内核代码的跟踪或处理。Kprobes接口
之前必须使用c语言写入口处理函数和返回处理函数然后调用register_kprobe来注册。
现在主要使用BCC 或者bpftrace。
BCC提供了
attach_kprobe()
attach_kretprobe() bpftrace 的一个demo 如下
bpftrace -e kprobe:vfs_* {[probe] count()}
uprobes
概念
提供了用户态程序的动态插桩。
和kprobes类似原理和kprobes 也类似。
原理
通过在指定的地址处插入一条跳转指令来截获进入或离开该地址的CPU执行流程并在其前后执行指定的处理程序。这种方法可以在不破坏二进制代码的情况下实现对程序的运行状态进行监控和修改 接口
基于Ftrace 向/sys/kernel/debug/tracing/uprobe_events: 通过对这个文件写入特定字符串的打开或者关闭uprobes
perf_event_open()
BCC 中提供了两个接口
attach_uprobe
attach_uretprobe
跟踪点 tracepoint
概念
静态插桩
原理
在编写内核代码时开发者可以使用tracepoint宏定义来预定义跟踪点。这些跟踪点及其参数在编译时就已经被固定在内核二进制代码中了。
在程序运行时如果开启了跟踪功能当程序执行到相应的跟踪点时会触发与之对应的tracepoint回调函数。这个回调函数可以在代码中定义用来实现所需要的操作。由于跟踪点已经事先定义好了因此可以避免插入额外的汇编指令从而减少了对程序运行性能的影响。 接口
BCC提供了
tracepoint_probe()
USDT
概念
用户态预定义静态跟踪, 提供了一种用户空间的跟踪点机制 BPF 与USDT
USDT().enable_probe()
性能监控计数器
性能监控计数器是用于测量程序运行过程中各种系统硬件状态的计数器。它们通常由处理器底层硬件提供在系统中大量运行的计数器可以用于监控系统的性能和瓶颈。性能监控计数器可以衡量诸如指令执行、CPU缓存性能、内存汇总等方面的各项指标进而评估整个系统的性能和优化方案的效果。
在软件开发过程中使用性能监控计数器可以比较精确地分析程序的性能瓶颈。开发人员可以结合不同的计数器指标确定系统瓶颈所在并通过优化代码和算法等方式提高程序的性能。
常见的性能监控计数器包括CPU运行周期Clocks、指令执行数Instructions、缓存命中率Cache Hits、内存访问延迟Memory Latency等。这些计数器通常可以通过专用工具或系统命令行接口获取例如Linux系统提供的perf工具、Intel VTune、AMD CodeXL等性能监控工具。