BPF 打开文件统计
在Linux系统里想知道哪些文件实时被哪些程序打开怎么办于是开始学习BPF了。① 环境依赖检查与工具链安装内核版本至少在4.18以上; 可以使用uname -r命令查看当前内核版本。当然版本过低还得考虑升级内核或者选择兼容性的旧版本追踪点。在任何阶段离开不了用户态的工具链它扮演着重要角色。需要安装LLVM/Clang用于将C语言的BPF代码编译以及需要使用libpdf库来处理程序的加载、映射管理。我的环境为 Debian GNU/Linux 12 (bookworm)检查内核信息uname-r6.1.0-18-amd64 这是一份BTF(BPF Type Format)文件 告诉BPF “内核里有哪些结构体、字段、类型以及它们的布局” 以及它能生成一份当前内核版本的描述数据结构的头文件vmlinux.h提供给bpf程序用ls-lh/sys/kernel/btf/vmlinux -r--r--r--1root root4.2M8月1811:28 /sys/kernel/btf/vmlinux需要下载依赖的工具sudoaptupdatesudoaptinstall-yclang llvm bpftool libbpf-dev libelf-dev zlib1g-dev检查工具的版本clang--versionDebian clang version14.0.6 Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /usr/bin bpftool version bpftool v7.1.0 using libbpf v1.1 features: libbpf_strict以上顺利执行的话说明关键命令就位了。此外确保当前用户具有root权限。② BPF 程序核心概念与统计原理传统的监控方式往往采用轮询的方式开销大易错失某些时段的数据。而BPF采用的是推送的模式利用内核中的tracepoints或者kprobe机制当特定事件发生时(文件打开、读写)内核主动触发我们编写的BPF程序执行。BPF有两个主要核心概念: Hook 和 Map;Hook是内核中预定义的埋点Hook可通过/sys/kernel/tracing/events/查看或者使用bpftrace -l命令, 如sys_enter_openat。当程序访问这些系统调用时BPF程序会被瞬间唤醒采集数据通过Map把采样数据交给用户态的程序处理。注意BPF对指令数和循环有严格限制保证不会拖慢内核因此编写的BPF程序必须极其轻量仅负责采集关键数据如进程PID、线程TID、文件名并存入Map。Map是内核态和用户态共享的内存区域相当于数据的缓冲区。编写的BPF程序挂在内核中采集数据用户态的处理程序通过Map获取这些数据并处理这种设计实现了数据采集和处理的解耦。由内核高效记录其它复杂的处理排序、格式化交由用户态程序完成。在本例中入门级的实现实时监控哪些文件被哪些程序打开了。bpftrace-ltracepoint:syscalls:*openat*tracepoint:syscalls:sys_enter_openat tracepoint:syscalls:sys_enter_openat2 tracepoint:syscalls:sys_exit_openat tracepoint:syscalls:sys_exit_openat2ls-1hd/sys/kernel/tracing/events/syscalls/*openat* /sys/kernel/tracing/events/syscalls/sys_enter_openat /sys/kernel/tracing/events/syscalls/sys_enter_openat2 /sys/kernel/tracing/events/syscalls/sys_exit_openat /sys/kernel/tracing/events/syscalls/sys_exit_openat2③ 编写监控打开文件一个标准的 BPF 应用通常包含两部分嵌入在内核态的 C 语言代码.bpf.c和运行在用户态的控制逻辑.c或使用 Python/Lua 的 BCC 封装。为了便于理解和移植这里我们采用纯 C libbpf 的方式展示最原生的实现逻辑。首先生成vmlinux.h文件是所有bpf都要使用的文件bpftool btf dumpfile/sys/kernel/btf/vmlinuxformatcvmlinux.h定义file_open.h文件提供给用户态的代码、内核态的代码使用#ifndef__FILE_OPEN_H#define__FILE_OPEN_H#defineTASK_COMM_LEN16#defineFILENAME_LEN256structevent{unsignedintpid;unsignedinttid;charcomm[TASK_COMM_LEN];charfilename[FILENAME_LEN];};#endif定义内核态代码file_open.bpf.c。我们需要引入必要的头文件定义存储数据的结构体并编写挂载到追踪点的函数。以下代码片段展示了如何捕获sys_enter_openat事件打开文件时触发/*file_open.bpf.c*/#includevmlinux.h#includebpf/bpf_helpers.h#includebpf/bpf_tracing.h#includefile_open.h/*定义许可类型为GPL*/charLICENSE[]SEC(license)GPL;/*定义Map, 类型为BPF_MAP_TYPE_RINGBUF, 最大缓冲区16M, 可容纳元素大约59918*/struct{__uint(type,BPF_MAP_TYPE_RINGBUF);__uint(max_entries,124);}eventsSEC(.maps);/*挂到sys_enter_openat发生时触发该函数*/SEC(tracepoint/syscalls/sys_enter_openat)inthandle_openat(structtrace_event_raw_sys_enter*ctx){structevent*e;/*在ringbuf中, 获取可用的内存, 存放event*/ebpf_ringbuf_reserve(events,sizeof(*e),0);if(!e)return0;/*采集数据: TGID, TID, PROGRAM, FILENAME*/e-pidbpf_get_current_pid_tgid()32;e-tidbpf_get_current_pid_tgid()0xffffffff;bpf_get_current_comm(e-comm,sizeof(e-comm));/*把用户态的filename读到内核态的filename, 不能直接赋值, 用户态的地址是虚拟的*/if(bpf_probe_read_user_str(e-filename,sizeof(e-filename),(constchar*)ctx-args[1])0){bpf_ringbuf_discard(e,0);return0;}/*存放到ringbuf*/bpf_ringbuf_submit(e,0);return0;}这段代码的核心在于handle_openat函数它允许我们在sys_enter_openat函数发生时介入我们通过bpf_probe_read_user_str安全地读取用户态的内存中的文件名字符串避免直接访问虚拟的地址导致异常。数据被聚合到events中每个event对象包含进程PID 、线程TID、程序名和打开的文件。然后编译成file_open.bpf.o 这就是bBPF程序使用时会挂到内核里。clang-O2-g-targetbpf\-D__TARGET_ARCH_x86\-I.\-cfile_open.bpf.c\-ofile_open.bpf.o到这一步很重要用bpftool命令把已经编译好的 BPF 程序 file_open.bpf.o生成一个给用户态 C/C 程序使用的“操作封装头文件” file_open.skel.h。此处就不再赘述自动生成的代码。偷懒下嘻嘻。bpftool gen skeleton file_open.bpf.ofile_open.skel.h用户态代码主要负责加载此 BPF 对象、轮询 Map 内容并格式化打印逻辑相对标准。/*file_open.c*/#includesignal.h#includeunistd.h#includebpf/libbpf.h#includefile_open.h#includefile_open.skel.hstaticvolatilesig_atomic_texiting0;staticvoidsig_handler(intsig){exiting1;}staticinthandle_event(void*ctx,void*data,size_tdata_sz){structevent*edata;printf(%-8u %-8u [%-16s] %s\n,e-pid,e-tid,e-comm,e-filename);return0;}intmain(void){structfile_open_bpf*skel;structring_buffer*rbNULL;interr;signal(SIGINT,sig_handler);signal(SIGTERM,sig_handler);/*创建skeleton对象打开BPF程序加载到内核*/skelfile_open_bpf__open_and_load();if(!skel){fprintf(stderr,failed to open/load BPF skeleton\n);return1;}/*将BPF程序挂到指定的HOOK点*/errfile_open_bpf__attach(skel);if(err){fprintf(stderr,failed to attach BPF program: %d\n,err);gotocleanup;}/*libpdf的ring buffer api*//*创建map缓冲区并监控fd对应的bpf map并绑定回调handle_event处理*/rbring_buffer__new(bpf_map__fd(skel-maps.events),handle_event,NULL,NULL);if(!rb){fprintf(stderr,failed to create ring buffer\n);err1;gotocleanup;}printf(Tracing file opens... Ctrl-C to exit.\n);while(!exiting){/*等待bpf map新数据超时100ms*/errring_buffer__poll(rb,100);if(err0err!-EINTR){fprintf(stderr,ring_buffer__poll: %d\n,err);break;}}cleanup:ring_buffer__free(rb);file_open_bpf__destroy(skel);returnerr0?-err:err;}用户态与内核态交互流程图 user program | ┌───────────────────────────────┐ │ BPF Ring Buffer │ │ │ │ event │ event │ event │ ... │ └───────────────────────────────┘ | kernel bpf program④ 编译加载与实时运行操作代码编写完成后需要将其编译为 BPF 字节码并加载到内核中。编译过程已被我封裝成Makefile自动化处理。Makefile文件VMLINUX_CMD bpftool btf dump file /sys/kernel/btf/vmlinux format c vmlinux.h CLANG_CMD clang -O2 -g -target bpf \ -D__TARGET_ARCH_x86 \ -I. \ -c file_open.bpf.c \ -o file_open.bpf.o BPFTOOL_CMD bpftool gen skeleton file_open.bpf.o file_open.skel.h MAIN_CMD clang -O2 -g \ -I. \ file_open.c \ -o file_open \ -lbpf -lelf -lz CLEAN_OPTION : clean distclean all: $(CLANG_CMD) ${BPFTOOL_CMD} ${MAIN_CMD} clean: rm -rf *.o .PHONY: ${CMD_OPTION}执行命令make编译成功后会生成一个file_open.bpf..o结尾的目标文件和用户态的程序file_open。运行它需要 root 权限sudo./file_open程序启动后会立即把BPF程序在内核中挂到对应的HOOK。此时你可以打开另一个终端窗口执行一些文件操作比如使用cat读取大文件。回到运行file_open的窗口你应该能看到屏幕开始滚动输出实时的输出打开文件信息。如果需要停止监控只需在终端按下CtrlC程序会自动清理内核中的BPF程序确保不留任何痕迹。这种“即插即用”的特性正是 eBPF 相比传统内核模块的巨大优势。⑤ 解读统计输出与数据验证当程序运行一段时间后终端会输出类似以下的统计表格Tracing file opens... Ctrl-C to exit. 1725 1889 [nxserver.bin ] /proc/1725/stat 319703 319703 [com.alibabainc. ] /sys/devices/system/cpu/online 3599581 3599581 [sh ] /etc/ld.so.cache 3599581 3599581 [sh ] /lib/x86_64-linux-gnu/libc.so.6 3599583 3599583 [tmux ] /etc/ld.so.cache 3599583 3599583 [tmux ] /lib/x86_64-linux-gnu/libutempter.so.0 3599583 3599583 [tmux ] /lib/x86_64-linux-gnu/libtinfo.so.6 3599583 3599583 [tmux ] /lib/x86_64-linux-gnu/libevent_core-2.1.so.7 3599583 3599583 [tmux ] /lib/x86_64-linux-gnu/libm.so.6 3599583 3599583 [tmux ] /lib/x86_64-linux-gnu/libresolv.so.2 3599583 3599583 [tmux ] /lib/x86_64-linux-gnu/libc.so.6 2732580 2732598 [ThreadPoolForeg ] /etc/hosts第一列为进程PID第二列为线程TID第三列为程序名第四列为被打开的文件。你可以打开cat /etc/hosts确认是否会被采集到。可以通过bpftool prog show查看是否挂载成功。bpftool prog show|grephandle_openat-A41404: tracepoint name handle_openat tag 4c3130baddf766e0 gpl loaded_at2026-08-18T15:42:160800 uid0xlated 256B jited 141B memlock 4096B map_ids119btf_id402最后想知道TGID为啥是进程PID的话请看该文章《错宗复杂的进程标识PID》—越简单易接受。在折腾路上…

相关新闻

Rust+Slint 实现的“DNA双螺旋”加载动画源码分享

Rust+Slint 实现的“DNA双螺旋”加载动画源码分享

RustSlint 实现的“DNA双螺旋”加载动画源码分享一、效果展示二、源码分享1、工程结构2、main.rs3、build.rs4、main.slint5、Cargo.toml三、实现原理1、 整体架构与数据流2、 核心动画原理:正弦波与径向摆动2.1、 位置与角度固定2.2 、半径计算函数2.3、 动画驱动3…

2026/11/14 5:25:47 阅读更多 →
Codex MCP 首次配置实战:别想一步到位,先跑通最小闭环

Codex MCP 首次配置实战:别想一步到位,先跑通最小闭环

引言:为什么“最小闭环”比“大而全”更重要? 在初次接触 Codex 的 Model Context Protocol (MCP) 时,很多开发者容易陷入一个误区:试图一次性配置所有高级功能,如本地 STDIO、远程 HTTP、OAuth 认证、复杂权限和模型…

2026/11/6 9:22:17 阅读更多 →
这几个Go项目的坑都给大家总结好了,不要去踩了

这几个Go项目的坑都给大家总结好了,不要去踩了

过去几年,我花了很多时间看 Go 代码。有时候是为了搞清楚一个库怎么用,有时候是面试候选人,有时候单纯是想偷点设计灵感。不知不觉,看了几百个项目——有 GitHub 上几十个 star 的练手作品,也有每天处理百万请求的生产…

2026/11/6 1:57:39 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/14 5:13:29 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/14 5:13:29 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/14 5:13:29 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/13 22:08:41 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/14 2:41:37 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/14 5:13:29 阅读更多 →

日新闻

周新闻

月新闻