BCC小白写测试上下文切换延时程序

一口Linux 2023-03-28 11:50


作者介绍:

张子恒,西安邮电大学研一在读,导师陈莉君老师,刚刚踏入Linux内核学习的小白一枚。



下文Linux内核版本5.4

0



启发



1

学习兴趣起源于论文《Linux上下文切换性能测试的一种新方法》的阅读,该论文提出了一种在用户态编写应用程序并且调用schedu_yield()系统调用主动放弃处理器实现任务切换的测试方法,然后与传统的使用管道读写切换、在内核态测试context_switch()函数的开销等方法进行了对比分析(传统方法分析见下图),最后结果表明,使用该方法测试上下文切换的准确性和便捷性均有所提高。

论文中提出的新方法有个明显的缺陷:原理上获得的上下文切换延时还包含了系统调用等其他指令的开销 overhead,但是他这个用户态的程序无法测量也无法避免这个overhead。

结合所学知识,受此启发:直接对内核函数context_switch()进行测试才是最准确最可靠的方法,因此可以写一个ebpf程序直接对context_switch()函数进行测试,这样的ebpf程序不仅测试出来的结果更加准确,而且操作起来也很方便,于是就有了下面的学习历程。

0



思路



2

首先是整理思路,作为eBPF小白的我一开始也是一筹莫展,于是翻看起了《BPF之巅  洞悉Linux系统和应用性能》这本书,书中有这么几个地方很受启发:

  • P57:

由于:

内核中没有context_switch的静态跟踪点,所以这里选择用kprobes动态跟踪。(这里忘记对kprobe进行查询,导致下面绕了一波远路,这也提醒学者在写eBPF程序之前一定要先找探针)

  • P49:

kprobes 可以对任何内核函数进行插桩,它还可以对函数内部的指令进行插桩。它可以实时在生产环境系统中启用,不需要重启系统,也不需要以特殊方式重启内核。这是一项令人惊叹的能力,这意味着我们可以对 Linux 中数以万计的内核函数任意插桩根据需要生成指标。

kprobes 技术还有另外一个接口,即 kretprobes,用来对内核函数返回时进行插桩以获取返回值。当用 kprobes 和 kretprobes 对同一个函数进行插时,可以使用时间戳来记录函数执行的时长。这在性能分析中是一个重要的指标。

  • P52:

BCC:attach_kprobe 和 attach_kretprobe

于是我就准备从kprobes和kretprobes这两种探针入手,对context_switch函数的执行时长进行测量。

0



实践



3

最后就是实践了,我利用不同的映射输出函数写了三个版本,每个版本都是上一个版本的优化。

(一)起初代码

主要涉及函数:

  • bpf_trace_printk()

    语法: int bpf_trace_printk(const char *fmt, ...)

    Return: 0 on success

    printf()到公共trace_pipe (/sys/kernel/debug/tracing/trace_pipe)的一个简单的内核工具。对于一些快速示例,这是可以的,但有限制:最多3个args,只有1% s,并且trace_pipe是全局共享的。

  • trace_print()

    语法: BPF.trace_print(fmt="fields")

    该方法持续读取全局共享的/sys/kernel/debug/tracing/trace_pipe文件并打印其内容。可以通过BPF和bpf_trace_printk()函数写入该文件。

    fmt: 可选,并且可以包含字段格式字符串。默认为None

cs.c 代码如下:

#include 

BPF_HASH(start, u32);

int do_entry(struct pt_regs *ctx)        //pt_regs结构定义了在系统调用或其他内核条目期间将寄存器存储在内核堆栈上的方式
{
        u32 pid;
        u64 ts;

        pid = bpf_get_current_pid_tgid();
        ts = bpf_ktime_get_ns();         //bpf_ktime_get_ns返回自系统启动以来所经过的时间(以纳秒为单位)。不包括系统挂起的时间。
        start.update(&pid,&ts);
        return 0;
}

int do_return(struct pt_regs *ctx)
{
        u32 pid;
        u64 *tsp, delta;

        pid = bpf_get_current_pid_tgid();
        tsp = start.lookup(&pid);

        if (tsp != 0) {
                delta = bpf_ktime_get_ns() - *tsp;         //获得context_switch函数执行时间
                bpf_trace_printk("The time of context_switch is %lu.\n",delta/1000);
                start.delete(&pid);
}

        return 0;
}

cs.py 代码如下:

from __future__ import print_function
from bcc import BPF

# load BPF program
b = BPF(src_file = "cs.c")
b.attach_kprobe(event="context_switch", fn_name="do_entry")
b.attach_kretprobe(event="context_switch", fn_name="do_return")

b.trace_print()

运行结果,遇到如下问题:

一开始以为是因为context_switch函数没有被导出的原因,经师兄提醒,查看了下探针:

发现没有context_switch的kprobe,相关函数学习:

nr_context_switches             //统计目前所有处理器总共的context switch次数
paravirt_end_context_switch         //和半虚拟化相关
paravirt_start_context_switch
rcu_note_context_switch         //更新全局状态为RCU,标识当前CPU发生上下文的切换
xen_end_context_switch         //和虚拟话技术相关

这些函数和上下文切换时间的计算都关联不上。

教训:在编写BCC程序时,一定要先用bpftrace查看一下该插桩点是否存在!

(二)纠正代码

退一步,如果无法对context_switch函数进行跟踪,那么对内核函数schedule进行跟踪就是最好地选择(不存在__schedule函数的探针),虽然存在一定的误差,但是相比于通过用户态程序测量更加准确,相比于在内核里插桩测试更加简便,经查询,schedule函数只存在动态插桩点位:

schedule() 源码:

asmlinkage __visible void __sched schedule(void)
{
        struct task_struct *tsk = current;

        sched_submit_work(tsk);         //sched_submit_work用于检测当前进程是否有plugged io需要处理,由于当前进程执行schedule后,有可能会进入休眠,所以在休眠之前需要把plugged io处理掉,防止死锁。

        do {

                preempt_disable();         //禁止抢占

                __schedule(false);         //->context_switch false为禁用抢占标志
                sched_preempt_enable_no_resched();         //开启内核抢占
        } while (need_resched());         //检查当前进程是否设置了重调度标志
        sched_update_worker(tsk);     //更新worker的信息,告诉工作队列我又回来了
}

对于数据的输出,由于bpf_trace_printk()是把数据写到公共trace_pipe,可能与其他程序和跟踪器冲突,因此选用BPF_PERF_OUTPUT()进行数据的输出,它的工作原理是创建一个BPF表,用于通过缓冲区向用户空间推出自定义事件数据。这是将每个事件数据推入用户空间的首选方法。

主要涉及函数:

  • BPF_PERF_OUTPUT

    语法: BPF_PERF_OUTPUT(name)

    创建一个BPF表,用于通过缓冲区向用户空间推出自定义事件数据。这是将每个事件数据推入用户空间的首选方法。

  • perf_submit()

    语法: int perf_submit((void *)ctx, (void *)data, u32 data_size)

    Return: 0 on success

    BPF_PERF_OUTPUT表的方法,用于向用户空间提交自定义事件数据。

    ctx参数在kprobes或kretprobes中提供。对于SCHED_CLSSOCKET_FILTER程序,必须改用struct __sk_buff *skb

  • open_perf_buffer()

    语法: table.open_perf_buffers(callback, page_cnt=N, lost_cb=None)

    它对BPF中定义为BPF_PERF_OUTPUT()的表进行操作,并关联回调Python函数callback,以便在性能环缓冲区中有可用数据时调用。这是将每个事件数据从内核传输到用户空间的推荐机制的一部分。缓冲区的大小可以通过page_cnt参数指定,该参数必须是两页数的幂,默认为8。如果回调处理数据的速度不够快,一些提交的数据可能会丢失。lost_cb将被调用来记录/监视丢失的计数。如果lost_cb 是默认的None值,它将只打印一行消息到stderr

  • perf_buffer_poll()

    语法: BPF.perf_buffer_poll(timeout=T)

    它从所有打开的性能环缓冲区轮询,调用为每个条目调用open_perf_buffer时提供的回调函数。

    timeout参数是可选的,以毫秒为单位。如果没有它,轮询将无限期地继续进行。

cs.c 代码如下:

#include 

struct data_t{
        u64 t1;
        u64 t2;
        u64 delay;
};

BPF_PERF_OUTPUT(events);
BPF_HASH(start, u32);

int do_entry(struct pt_regs *ctx)        //pt_regs结构定义了在系统调用或其他内核条目期间将寄存器存储在内核堆栈上的方式
{
        u32 pid;
        u64 ts;

        pid = bpf_get_current_pid_tgid();
        ts = bpf_ktime_get_ns()/1000;         //bpf_ktime_get_ns返回自系统启动以来所经过的时间(以纳秒为单位)。不包括系统挂起的时间。
        start.update(&pid,&ts);

        return 0;
}

int do_return(struct pt_regs *ctx)
{
        struct data_t data={};
        data.t2= bpf_ktime_get_ns()/1000;
        u32 pid;
        u64 *tsp, delta;

        pid = bpf_get_current_pid_tgid();
        tsp = start.lookup(&pid);

        if (tsp != 0) {

                data.t1 = *tsp;

                data.delay = data.t2 - data.t1;
                start.delete(&pid);
                events.perf_submit(ctx,&data,sizeof(data));
        }

                return 0;
}

cs.py 代码如下:

from __future__ import print_function
from bcc import BPF
import time

# load BPF program
b = BPF(src_file = "cs.c")
b.attach_kprobe(event="schedule", fn_name="do_entry")
b.attach_kretprobe(event="schedule", fn_name="do_return")

print("Tracing for Data's... Ctrl-C to end")

#声明了一个名为print_event()的回调函数,用于处理来自perf缓冲区的一个事件
def print_event(cpu, data, size):
        event = b["events"].event(data)
        print("t1:%d t2:%d delay:%d" % (event.t1,event.t2,event.delay))

#将回调函数注册到名为events的perf事件缓冲区
b["events"].open_perf_buffer(print_event)

while 1:

        try:

                b.perf_buffer_poll()        #轮询打开perf缓冲区。如果有事件,回调函数会执行
                time.sleep(1)
        except KeyboardInterrupt:        #Python的except用来捕获所有异常, 因为Python里面的每次错误都会抛出 一个异常,所以每个程序的错误都被当作一个运行时错误。捕获指定异常except <异常名>
                exit()

cs.py 代码中time.sleep(1)说明:由于上下文切换延时特别小,因此schedule函数的触发非常频繁,每次b.perf_buffer_poll()的输出数据都特别多,会导致ctrl+c很难抢占到CPU对这个程序关闭,所以得加入time.sleep(1)以使得能有时间间隙去关闭该程序

避坑:加载到内核中的代码(这里即cs.c )不要用全局变量,用map相关函数

运行结果:

弊端:虽然可以正常运行,但是事件发生地很频繁,导致输出过快、输出量过大,不能直观的了解到数据的情况。

(三)优化代码

可以对事件进行汇总后再输出,打印为以2为幂的直方图。

主要涉及函数:

  • BPF_HISTOGRAM

    语法: BPF_HISTOGRAM(name [, key_type [, size ]])

    创建一个名为name的直方图映射,带有可选参数。

    默认: BPF_HISTOGRAM(name, key_type=int, size=64)

  • map.increment()

    语法: map.increment(key[, increment_amount])

    按 increment_amount增加键的值,默认为1。用于直方图。

  • bpf_log2l()

    语法: unsigned int bpf_log2l(unsigned long v)

    返回所提供值的log-2。这通常用于为直方图创建索引,以构造2次方的直方图。

  • print_log2_hist()

    语法: table.print_log2_hist(val_type="value", section_header="Bucket ptr", section_print_fn=None)

    将表打印为ASCII中的log2直方图。表必须存储为log2,这可以使用BPF函数 bpf_log2l()来实现。

    参数:val_type: 可选,列标题;section_header: 如果直方图有一个辅助键,那么将打印多个表,section_header可以用作每个表的头描述;section_print_fn: 如果section_print_fn不是None,它将被传递桶值。

cs.c 代码如下:

#include 

BPF_HASH(start, u32);
BPF_HISTOGRAM(dist);

int do_entry(struct pt_regs *ctx)        //pt_regs结构定义了在系统调用或其他内核条目期间将寄存器存储在内核堆栈上的方式
{
        u64 t1= bpf_ktime_get_ns()/1000;         //bpf_ktime_get_ns返回自系统启动以来所经过的时间(以纳秒为单位)。不包括系统挂起的时间。;
        u32 pid = bpf_get_current_pid_tgid();
        start.update(&pid,&t1);

        return 0;
}

int do_return(struct pt_regs *ctx)
{
        u64 t2= bpf_ktime_get_ns()/1000;
        u32 pid;
        u64 *tsp, delay;

        pid = bpf_get_current_pid_tgid();
        tsp = start.lookup(&pid);

        if (tsp != 0)
        {
                delay = t2 - *tsp;
                start.delete(&pid);
                dist.increment(bpf_log2l(delay));
        }

        return 0;
}

cs.py 代码如下:

from __future__ import print_function
from bcc import BPF
from time import sleep

# load BPF program
b = BPF(src_file = "cs.c")
b.attach_kprobe(event="schedule", fn_name="do_entry")
b.attach_kretprobe(event="schedule", fn_name="do_return")

print("Tracing for Data's... Ctrl-C to end")

#trace until Ctrl-C
try:
        sleep(99999999)
except KeyboardInterrupt:
        print()

#output
b["dist"].print_log2_hist("cs delay")

运行结果:

疑惑:这个运行结果和上面的那个运行结果差别比较大,这个运行结果的的值总体上偏大,而上面那个运行结果的值更符合常理,这个运行结果不应该是对上面那个运行结果的统计输出吗?为什么差别这么大呢?

解决:考虑到可能是不同进程环境导致的,于是我同时运行这两个测试程序,以保证进程环境的相同,得到以下运行结果:

经过数据对比,在数值上,这些数据是对的上的,说明优化后的测试程序就是对优化前的测试程序数据的统计输出,之前单独运行这两个测试程序的运行结果不同就是因为不同进程环境导致的。

0



参考资料



4

  1. bcc Reference Guide https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md

  2. 《BPF之巅  洞悉Linux系统和应用性能》

end


一口Linux 


关注,回复【1024】海量Linux资料赠送

精彩文章合集


文章推荐

【专辑】ARM
【专辑】粉丝问答
专辑linux入门
专辑计算机网络
专辑Linux驱动
【干货】嵌入式驱动工程师学习路线
【干货】Linux嵌入式所有知识点-思维导图

一口Linux 写点代码,写点人生!
评论
  •  万万没想到!科幻电影中的人形机器人,正在一步步走进我们人类的日常生活中来了。1月17日,乐聚将第100台全尺寸人形机器人交付北汽越野车,再次吹响了人形机器人疯狂进厂打工的号角。无独有尔,银河通用机器人作为一家成立不到两年时间的创业公司,在短短一年多时间内推出革命性的第一代产品Galbot G1,这是一款轮式、双臂、身体可折叠的人形机器人,得到了美团战投、经纬创投、IDG资本等众多投资方的认可。作为一家成立仅仅只有两年多时间的企业,智元机器人也把机器人从梦想带进了现实。2024年8月1
    刘旷 2025-01-21 11:15 666浏览
  •  光伏及击穿,都可视之为 复合的逆过程,但是,复合、光伏与击穿,不单是进程的方向相反,偏置状态也不一样,复合的工况,是正偏,光伏是零偏,击穿与漂移则是反偏,光伏的能源是外来的,而击穿消耗的是结区自身和电源的能量,漂移的载流子是 客席载流子,须借外延层才能引入,客席载流子 不受反偏PN结的空乏区阻碍,能漂不能漂,只取决于反偏PN结是否处于外延层的「射程」范围,而穿通的成因,则是因耗尽层的过度扩张,致使跟 端子、外延层或其他空乏区 碰触,当耗尽层融通,耐压 (反向阻断能力) 即告彻底丧失,
    MrCU204 2025-01-17 11:30 210浏览
  •     IPC-2581是基于ODB++标准、结合PCB行业特点而指定的PCB加工文件规范。    IPC-2581旨在替代CAM350格式,成为PCB加工行业的新的工业规范。    有一些免费软件,可以查看(不可修改)IPC-2581数据文件。这些软件典型用途是工艺校核。    1. Vu2581        出品:Downstream     
    电子知识打边炉 2025-01-22 11:12 134浏览
  • 数字隔离芯片是一种实现电气隔离功能的集成电路,在工业自动化、汽车电子、光伏储能与电力通信等领域的电气系统中发挥着至关重要的作用。其不仅可令高、低压系统之间相互独立,提高低压系统的抗干扰能力,同时还可确保高、低压系统之间的安全交互,使系统稳定工作,并避免操作者遭受来自高压系统的电击伤害。典型数字隔离芯片的简化原理图值得一提的是,数字隔离芯片历经多年发展,其应用范围已十分广泛,凡涉及到在高、低压系统之间进行信号传输的场景中基本都需要应用到此种芯片。那么,电气工程师在进行电路设计时到底该如何评估选择一
    华普微HOPERF 2025-01-20 16:50 123浏览
  • 飞凌嵌入式基于瑞芯微RK3562系列处理器打造的FET3562J-C全国产核心板,是一款专为工业自动化及消费类电子设备设计的产品,凭借其强大的功能和灵活性,自上市以来得到了各行业客户的广泛关注。本文将详细介绍如何启动并测试RK3562J处理器的MCU,通过实际操作步骤,帮助各位工程师朋友更好地了解这款芯片。1、RK3562J处理器概述RK3562J处理器采用了4*Cortex-A53@1.8GHz+Cortex-M0@200MHz架构。其中,4个Cortex-A53核心作为主要核心,负责处理复杂
    飞凌嵌入式 2025-01-24 11:21 18浏览
  • 本文介绍瑞芯微开发板/主板Android配置APK默认开启性能模式方法,开启性能模式后,APK的CPU使用优先级会有所提高。触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。源码修改修改源码根目录下文件device/rockchip/rk3562/package_performance.xml并添加以下内容,注意"+"号为添加内容,"com.tencent.mm"为AP
    Industio_触觉智能 2025-01-17 14:09 203浏览
  • 嘿,咱来聊聊RISC-V MCU技术哈。 这RISC-V MCU技术呢,简单来说就是基于一个叫RISC-V的指令集架构做出的微控制器技术。RISC-V这个啊,2010年的时候,是加州大学伯克利分校的研究团队弄出来的,目的就是想搞个新的、开放的指令集架构,能跟上现代计算的需要。到了2015年,专门成立了个RISC-V基金会,让这个架构更标准,也更好地推广开了。这几年啊,这个RISC-V的生态系统发展得可快了,好多公司和机构都加入了RISC-V International,还推出了不少RISC-V
    丙丁先生 2025-01-21 12:10 617浏览
  • 现在为止,我们已经完成了Purple Pi OH主板的串口调试和部分配件的连接,接下来,让我们趁热打铁,完成剩余配件的连接!注:配件连接前请断开主板所有供电,避免敏感电路损坏!1.1 耳机接口主板有一路OTMP 标准四节耳机座J6,具备进行音频输出及录音功能,接入耳机后声音将优先从耳机输出,如下图所示:1.21.2 相机接口MIPI CSI 接口如上图所示,支持OV5648 和OV8858 摄像头模组。接入摄像头模组后,使用系统相机软件打开相机拍照和录像,如下图所示:1.3 以太网接口主板有一路
    Industio_触觉智能 2025-01-20 11:04 194浏览
  • 日前,商务部等部门办公厅印发《手机、平板、智能手表(手环)购新补贴实施方案》明确,个人消费者购买手机、平板、智能手表(手环)3类数码产品(单件销售价格不超过6000元),可享受购新补贴。每人每类可补贴1件,每件补贴比例为减去生产、流通环节及移动运营商所有优惠后最终销售价格的15%,每件最高不超过500元。目前,京东已经做好了承接手机、平板等数码产品国补优惠的落地准备工作,未来随着各省市关于手机、平板等品类的国补开启,京东将第一时间率先上线,满足消费者的换新升级需求。为保障国补的真实有效发放,基于
    华尔街科技眼 2025-01-17 10:44 238浏览
  • 高速先生成员--黄刚这不马上就要过年了嘛,高速先生就不打算给大家上难度了,整一篇简单但很实用的文章给大伙瞧瞧好了。相信这个标题一出来,尤其对于PCB设计工程师来说,心就立马凉了半截。他们辛辛苦苦进行PCB的过孔设计,高速先生居然说设计多大的过孔他们不关心!另外估计这时候就跳出很多“挑刺”的粉丝了哈,因为翻看很多以往的文章,高速先生都表达了过孔孔径对高速性能的影响是很大的哦!咋滴,今天居然说孔径不关心了?别,别急哈,听高速先生在这篇文章中娓娓道来。首先还是要对各位设计工程师的设计表示肯定,毕竟像我
    一博科技 2025-01-21 16:17 159浏览
  • 故障现象 一辆2007款日产天籁车,搭载VQ23发动机(气缸编号如图1所示,点火顺序为1-2-3-4-5-6),累计行驶里程约为21万km。车主反映,该车起步加速时偶尔抖动,且行驶中加速无力。 图1 VQ23发动机的气缸编号 故障诊断接车后试车,发动机怠速运转平稳,但只要换挡起步,稍微踩下一点加速踏板,就能感觉到车身明显抖动。用故障检测仪检测,发动机控制模块(ECM)无故障代码存储,且无失火数据流。用虹科Pico汽车示波器测量气缸1点火信号(COP点火信号)和曲轴位置传感器信
    虹科Pico汽车示波器 2025-01-23 10:46 74浏览
  • 临近春节,各方社交及应酬也变得多起来了,甚至一月份就排满了各式约见。有的是关系好的专业朋友的周末“恳谈会”,基本是关于2025年经济预判的话题,以及如何稳定工作等话题;但更多的预约是来自几个客户老板及副总裁们的见面,他们为今年的经济预判与企业发展焦虑而来。在聊天过程中,我发现今年的聊天有个很有意思的“点”,挺多人尤其关心我到底是怎么成长成现在的多领域风格的,还能掌握一些经济趋势的分析能力,到底学过哪些专业、在企业管过哪些具体事情?单单就这个一个月内,我就重复了数次“为什么”,再辅以我上次写的:《
    牛言喵语 2025-01-22 17:10 178浏览
  • Ubuntu20.04默认情况下为root账号自动登录,本文介绍如何取消root账号自动登录,改为通过输入账号密码登录,使用触觉智能EVB3568鸿蒙开发板演示,搭载瑞芯微RK3568,四核A55处理器,主频2.0Ghz,1T算力NPU;支持OpenHarmony5.0及Linux、Android等操作系统,接口丰富,开发评估快人一步!添加新账号1、使用adduser命令来添加新用户,用户名以industio为例,系统会提示设置密码以及其他信息,您可以根据需要填写或跳过,命令如下:root@id
    Industio_触觉智能 2025-01-17 14:14 145浏览
  • 2024年是很平淡的一年,能保住饭碗就是万幸了,公司业绩不好,跳槽又不敢跳,还有一个原因就是老板对我们这些员工还是很好的,碍于人情也不能在公司困难时去雪上加霜。在工作其间遇到的大问题没有,小问题还是有不少,这里就举一两个来说一下。第一个就是,先看下下面的这个封装,你能猜出它的引脚间距是多少吗?这种排线座比较常规的是0.6mm间距(即排线是0.3mm间距)的,而这个规格也是我们用得最多的,所以我们按惯性思维来看的话,就会认为这个座子就是0.6mm间距的,这样往往就不会去细看规格书了,所以这次的运气
    wuliangu 2025-01-21 00:15 324浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦