美文网首页
透视Linux内核 神奇的BPF一

透视Linux内核 神奇的BPF一

作者: 明翼 | 来源:发表于2022-02-20 16:20 被阅读0次

    一 前言

    作为一个coder,时不时会遇到性能问题,有时候明明看资源,cpu,io都占用不高,程序的性能就是上不去,真有一种想进入到计算机里面看看到底发生什么的冲突;还有优化性能的时候不知道整个系统的短板到底是哪一块,如何去优化它?

    根本原因其实是对系统的内核不够了解,导致虽然有解决问题的激情和动力,但是总是难找到关键点,彷徨而不得其门。让我学习内核,却又望而退步,觉得难度还是太大,有没有不用深入了解系统内核,但是又能深入观察内核行为的办法那,这时候我发现了BPF和eBPF,通过它有了透视内核的能力,所以就开始了BPF学习之旅。

    二 BPF是个什么

    BPF原来是Berkely Packet Filter(伯克利数据包过滤器)的缩写,原来是提升pcap过滤性能的,比当时最快的包过滤技术快20倍,只所以性能高,是因为它工作在内核中,避免包从内核态复制到用户态所以速度快,后来Alexei Starovoitov 大牛在2014年重新实现了BPF,将其扩展成了通用的执行引擎,称为eBPF,官方缩写仍是BPF。

    简单解释BPF作用,BPF提供了一种当内核或应用特定事件发生时候,执行一段代码的能力。BPF 采用了虚拟机指令规范,所以也可以看一种虚拟机实现,使我们可以在不修改内核源码和重新编译的情况下,提供一种扩展内核的能力的方法。

    三 BPF能干嘛

    BPF程序不像一般程序可以独立运行,它是被动运行的,需要事件触发才能运行,有点类似js里面的监听,监听到按钮点击执行一小段代码。这些事件包括系统调用,内核跟踪,内核函数,用户函数,网络事件等。

    具体能干嘛那,作用还是很强大,可以进行系统故障诊断,因为其有透视内核的能力;网络性能优化,因为它可以在内核态接收网络包,并做修改和转发;系统安全,因为它可以中断非法连接等;性能监控,因为其透视能力,可以查看函数耗费时间从而我们可以知道问题到底出在哪里。
    如下图:


    来自ebpf.io

    四 BPF如何工作

    经典的BPF的工作模式是用户使用BPF虚拟机的指令集定义过滤表达式,传递给内核,由解释器运行,使得包过滤器可以直接在内核态工作,避免向用户态复制数据,从而提升性能,比如tcpdump的BPF过滤指令实例如下:

    [root@localhost ~]# tcpdump -d port 80
    (000) ldh      [12]
    (001) jeq      #0x86dd          jt 2    jf 10
    (002) ldb      [20]
    (003) jeq      #0x84            jt 6    jf 4
    (004) jeq      #0x6             jt 6    jf 5
    (005) jeq      #0x11            jt 6    jf 23
    (006) ldh      [54]
    (007) jeq      #0x50            jt 22   jf 8
    (008) ldh      [56]
    (009) jeq      #0x50            jt 22   jf 23
    (010) jeq      #0x800           jt 11   jf 23
    (011) ldb      [23]
    (012) jeq      #0x84            jt 15   jf 13
    (013) jeq      #0x6             jt 15   jf 14
    (014) jeq      #0x11            jt 15   jf 23
    (015) ldh      [20]
    (016) jset     #0x1fff          jt 23   jf 17
    (017) ldxb     4*([14]&0xf)
    (018) ldh      [x + 14]
    (019) jeq      #0x50            jt 22   jf 20
    (020) ldh      [x + 16]
    (021) jeq      #0x50            jt 22   jf 23
    (022) ret      #262144
    (023) ret      #0
    
    

    执行过程如下:


    经典BPF过滤指令执行过程

    后来又一位大牛EricDumazet在2011年7月发布的Linux 3.0中增加了JIT(即时编译),性能比解释执行更快,多像java的虚拟机,可以解释执行也可以即时编译执行。

    现在BPF的执行过程如下示意图:


    图片来自brendangregg.com
    1. 编写eBPF 代码。
    2. 将eBPF代码通过LLVM把编写的eBPF代码转成字节码;
    3. 通过bpf系统调用提交给系统内核;
    4. 内核通过验证器对代码做安全性验证(包括对无界循环的检查);
    5. 只有校验通过的字节码才会提交到JIT进行编译成可以直接执行的机器指令;
    6. 当事件发生时候,调用这些指令执行,将结果保存到map中。
    7. 用户程序通过映射来获取执行结果。

    四 BPF 和内核模块对比

    • BPF程序会进行安全检查,内核模块可能会引入Bug。
    • BPF程序不能随意调用内核函数,只能调用部分辅助函数。
    • BPF的栈空间最大为512个字节,不能扩大,只能借助map存储;
    • BPF程序可以一次编译到处运行,因为它依赖的辅助函数,映射表,BPF指令集属于稳定的API。

    五 编写BPF程序

    5.1 准备知识

    开发BPF指令显然不适合直接用BPF指令开发,所以大牛们开发了一些前端工具让我们可以更方便的开发,比如我们可以通过C来编写BPF程序,然后通过LLVM编译成BPF。

    当然还是负载,又有了BCC和bpftrace。BCC即BPF Compiler Collection,提供了开发BPF跟踪程序的高级框架,提供编写内核BPF程序的C语言环境,同时提供了许多高级语言的接口,比如pyhton等。同时BCC中提供了很多BPF工具,让我们可以方便使用用于性能分析和故障分析,在开发BPF程序之前可以看看。

    bpftrace编写单行程序或短小脚本更加适合,BCC适合编写复杂的脚本和作为后台进程使用。libbcc和libbpf为两者提供底层支持。

    BPF程序编写可以借助工具 BCC开发的动态追踪工具集

    5.2 环境准备

    我的测试环境是centos8.5版本,内核版本为4.18,而BPF最好用5.x版本的内核需要先升级下。

    [root@localhost ~]# cat /etc/centos-release
    CentOS Linux release 8.5.2111
    [root@localhost ~]# uname -a
    Linux localhost.localdomain 4.18.0-348.7.1.el8_5.x86_64 #1 SMP Wed Dec 22 13:25:12 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
    

    内核升级步骤:

    #1. 到[https://www.kernel.org/](https://www.kernel.org/)查看稳定的内核版本为5.16.10
    #2. 下载编译
    wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.16.10.tar.xz
    tar xvf linux-5.16.10.tar.xz 
    cd linux-5.16.10/
    uname -a
    cp /boot/config-4.18.0-348.7.1.el8_5.x86_64  .config
    #注释掉CONFIG_SYSTEM_TRUSTED_KEYS
    make  menuconfig
    #进入界面按tab 选择Load 加载.config ,在Save后即可用原来配置编译
    
    #编译内核核心
    make -j 4  
    make modules_install
    #安装内核核心
    make install
    grub2-set-default 0   #0表示 /boot/grub2/grub.cfg 文件中排在第一位的 menuentry 段
    reboot
    make modules_prepare
    make script
    make headers_install   INSTALL_HDR_PATH=/usr/include
    #安装bpf 实例
    make M=samples/bpf
    
    

    安装BPF相关库和工具:

    yum install libbpf-devel make clang llvm elfutils-libelf-devel bpftool bcc-tools bcc-devel
    
    1. llvm : 将eBPF程序编译成字节码工具。
    2. c代码构建工具make
    3. eBPF工具集BCC和它依赖的头文件。
    4. libelf库以及ebpf管理工具ebpftool。
    5. 用户程序通过BPF映射查询到BPF字节码的字节码运行结果。

    5.3 依赖BCC开发BPF的helloworld

    步骤如下:

    1. 用C语言开发一个eBPF程序;
    2. 用LLVM把eBPF程序编译成BPF字节码;
    3. 通过bpf系统调用,把BPF字节码提交给内核;
    4. 内核验证并运行BPF字节码,并把相应状态保存到BPF映射中;
    5. 用户程序通过 BPF 映射查询 BPF 字节码,得到执行结果;


      BPF开发执行过程

    这个流程一般比较麻烦,可以利用BCC来简化,用python脚本加载BPF程序,编译为字节码,并通过系统调用将BPF字节码,运行BPF字节码;

    5.3.1 用C开发一个eBPF程序

    int hello(void *ctx)
    {
        bpf_trace_printk("Hello, World!");
        return 0;
    }
    

    bpf_trace_printk 是常用的BPF辅助函数,它就是简单的打印一个字符串;不过eBPF输出是内核调试文件: /sys/kernel/debug/tracing/trace_pipe

    5.3.2 使用python和BCC开发BPF的加载程序

    #!/usr/bin/env python3
    # 1) 导入BCC库中的BPF模块
    from bcc import BPF
    
    # 2) 加载C程序开发的BPF程序
    b = BPF(src_file="hello.c")
    # 3) 将此BPF程序挂载到内核探针,其中do_sys_openat2是系统调用openat 在内核实现
    b.attach_kprobe(event="do_sys_openat2", fn_name="hello_world")
    # 4) 读取和打印 /sys/kernel/debug/tracing/trace_pipe
    b.trace_print()
    

    运行查看:

    > python3 hello.py
    
    b'       pmdalinux-1298    [007] d..31  6758.674383: bpf_trace_printk: Hello, World!'
    b'       pmdalinux-1298    [007] d..31  6758.674395: bpf_trace_printk: Hello, World!'
    b'       pmdalinux-1298    [007] d..31  6758.674410: bpf_trace_printk: Hello, World!'
    b'       pmdalinux-1298    [007] d..31  6758.674422: bpf_trace_printk: Hello, World!'
    b'       pmdalinux-1298    [007] d..31  6758.674426: bpf_trace_printk: Hello, World!'
    b'       python3-73326   [001] d..31  6758.674859: bpf_trace_printk: Hello, World!'
    b'      irqbalance-942     [006] d..31  6758.894331: bpf_trace_printk: Hello, World!'
    b'      irqbalance-942     [006] d..31  6758.894593: bpf_trace_printk: Hello, World!'
    
    

    问题解决

    问题一 编译过程磁盘空间满了

    按照[https://blog.csdn.net/xionglangs/article/details/108866146]扩展磁盘;(https://blog.csdn.net/xionglangs/article/details/108866146)

    问题二 make -j4 编译报错

    BTF: .tmp_vmlinux.btf: pahole (pahole) is not available
    Failed to generate BTF for vmlinux
    Try to disable CONFIG_DEBUG_INFO_BTF
    make: *** [Makefile:1106: vmlinux] Error 1
    

    解决办法:
    注释掉.config中的CONFIG_DEBUG_INFO_BTF 或 yum install dwarves

    问题三 编译需要支持bpf

     编译内核的时候bpf的编译选项打开,在.config文件中添加或修改
    CONFIG_BPF=y
    CONFIG_BPF_SYSCALL=y
    # [optional, for tc filters]
    CONFIG_NET_CLS_BPF=m
    # [optional, for tc actions]
    CONFIG_NET_ACT_BPF=m
    CONFIG_BPF_JIT=y
    # [for Linux kernel versions 4.1 through 4.6]
    CONFIG_HAVE_BPF_JIT=y
    # [for Linux kernel versions 4.7 and later]
    CONFIG_HAVE_EBPF_JIT=y
    # [optional, for kprobes]
    CONFIG_BPF_EVENTS=y
    # Need kernel headers through /sys/kernel/kheaders.tar.xz
    CONFIG_IKHEADERS=y
    
    
    CONFIG_NET_SCH_SFQ=m
    CONFIG_NET_ACT_POLICE=m
    CONFIG_NET_ACT_GACT=m
    CONFIG_DUMMY=m
    CONFIG_VXLAN=m
    

    问题四 make M=samples/bpf报错

    1. make M=samples/bpf报错
    /root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:322:63: error: unknown type name '__u32'
    static long (*bpf_tail_call)(void *ctx, void *prog_array_map, __u32 index) = (void *) 12;
                                                                  ^
    /root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:350:58: error: unknown type name '__u32'
    static long (*bpf_clone_redirect)(struct __sk_buff *skb, __u32 ifindex, __u64 flags) = (void *) 13;
                                                             ^
    fatal error: too many errors emitted, stopping now [-ferror-limit=]1. make M=samples/bpf报错
    /root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:322:63: error: unknown type name '__u32'
    static long (*bpf_tail_call)(void *ctx, void *prog_array_map, __u32 index) = (void *) 12;
                                                                  ^
    /root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:350:58: error: unknown type name '__u32'
    static long (*bpf_clone_redirect)(struct __sk_buff *skb, __u32 ifindex, __u64 flags) = (void *) 13;
                                                             ^
    fatal error: too many errors emitted, stopping now [-ferror-limit=]
    

    解决办法:

    vim /root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h
    添加头文件:
    #include <asm/types.h>
    #include <linux/types.h>
    
    

    问题五 failed to load BTF from /root/core/linux-5.16.10/vmlinux: No such file or directory

    Error: failed to load BTF from /root/core/linux-5.16.10/vmlinux: No such file or directory
    make[2]: *** [Makefile:179:/root/core/linux-5.16.10/samples/bpf/bpftool/vmlinux.h] 错误 2
    make[1]: *** [samples/bpf/Makefile:296:/root/core/linux-5.16.10/samples/bpf/bpftool/bpftool] 错误 2
    make: *** [Makefile:1846:samples/bpf] 错误 2
    [root@localhost linux-5.16.10]# 
    
    

    更改.config 配置:

    CONFIG_DEBUG_INFO_BTF=y
    make -j4
    

    问题六 fatal error: 'gnu/stubs-32.h' file not found

    升级:
    yum install glibc-devel
    yum install glibc-devel.i686
    
    

    参考

    [详细介绍了BPF程序编译生成字节码过程](https://www.cnblogs.com/lfri/p/15402973.html)
    [https://maao.cloud/2021/03/01/%E7%AC%94%E8%AE%B0-BPF-and-XDP-Reference-Guide-cilium/#LLVM](https://maao.cloud/2021/03/01/%E7%AC%94%E8%AE%B0-BPF-and-XDP-Reference-Guide-cilium/#LLVM)
    [技术|深入理解 BPF:一个阅读清单 (linux.cn)](https://linux.cn/article-9507-1.html)
    
    

    相关文章

      网友评论

          本文标题:透视Linux内核 神奇的BPF一

          本文链接:https://www.haomeiwen.com/subject/ayxplrtx.html