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 写点代码,写点人生!
评论
  • 概述随着工业4.0的深入推进,制造业对自动化和智能化的需求日益增长。传统生产线面临空间不足、效率低下、灵活性差等问题,尤其在现有工厂改造项目中,如何在有限空间内实现高效自动化成为一大挑战。此次项目的客户需要在现有工厂基础上进行改造,空间有限。为此,客户选择了SCARA型线性轴机器人作为执行设备。然而,SCARA机器人的高效运行离不开强大的控制系统支持。宏集凭借其先进的智能控制系统,为客户提供了高效、灵活的自动化解决方案,确保SCARA机器人在有限空间内发挥最大效能。一、客户需求在此次改造项目中,
    宏集科技 2025-03-06 11:27 120浏览
  • ASL6328芯片支持高达 6.0 Gbps 运行速率的交流和直流耦合输入T-MDS 信号,具备可编程均衡和抖动清理功能。ASL6328 是一款单端口 HDMI/DVI 电平转换 / 中继器,具有重新定时功能。它包含 TypeC双模式 DP 线缆适配器寄存器,可用于识别线缆适配器的性能。抖动清理 PLL(锁相环)能够消除输入抖动,并完全重置系统抖动容限,因此能更好地满足更高数据速率下 HDMI 抖动合规性要求。设备的运行和配置可通过引脚设置或 I2C 总线实现。自动断电和静噪功能提供了灵活的电
    QQ1540182856 2025-03-06 14:26 85浏览
  • 在当今竞争激烈的市场环境中,企业不仅需要优化成本,还需积极响应国家的能源政策,减少对环境的影响。提升工业能源效率正是实现这一双重目标的关键。中国近年来大力推进“双碳”目标(碳达峰、碳中和),并出台了一系列政策鼓励企业节能减排。通过宏集CODRA的Panorama解决方案,企业可以获得专为这一目标设计的SCADA工具,实时监控和调整所有工业设备的能耗。特别是其中的能源管理模块,能够有效分析数据,预防故障,避免能源浪费。Panorama的优化技术宏集CODRA提供的解决方案,尤其是Panorama
    宏集科技 2025-03-06 11:25 115浏览
  • 引言嘿,各位电动汽车的爱好者们!咱们今儿个就来聊聊电动汽车里那些“看不见,摸不着”,但又至关重要的零部件。要说电动汽车这玩意儿,那可真是科技含量满满,各种高精尖的技术都往里堆。但要让这些高科技玩意儿协同工作,稳定可靠地运转,那就得靠一些幕后英雄,比如说——电容器。你可能会想,电容器?这不就是电子电路里常见的元件嘛,能有多重要? 哎,你可别小瞧了这小小的电容器。在电动汽车的心脏地带——高压直流转换器(DC-DC转换器)里,车规级的电容器那可是扮演着举足轻重的角色。 今天,咱们就聚焦分析三星电机车规
    贞光科技 2025-03-05 17:02 90浏览
  • 随着自动驾驶技术的迅猛发展,构建高保真、动态的仿真场景成为了行业的迫切需求。传统的三维重建方法在处理复杂场景时常常面临效率和精度的挑战。在此背景下,3D高斯点阵渲染(3DGS)技术应运而生,成为自动驾驶仿真场景重建的关键突破。一、3DGS技术概述与原理1、3DGS的技术概述3DGS是一种基于3D高斯分布的三维场景表示方法。通过将场景中的对象转化为多个3D高斯点,每个点包含位置、协方差矩阵和不透明度等信息,3DGS能够精确地表达复杂场景的几何形状和光照特性。与传统的神经辐射场(NeRF)方法相比,
    康谋 2025-03-06 13:17 120浏览
  • 文/Leon编辑/侯煜‍2008至2021年间,创维以高举高打的凌厉之势,果断进行投资,一度成为中国市场大屏OLED产业的旗手,引领着显示技术的发展方向。但近年来,创维在 OLED 领域的发展轨迹却逐渐模糊,态度陷入暧昧不明的混沌状态。究其根源,一方面,创维对过往的押注难以割舍,在技术革新与市场变化的浪潮中,不愿轻易推翻曾经的战略布局;另一方面,早期在大屏OLED 技术研发、市场推广等环节投入的巨额资金,已然形成沉没成本,极大地限制了创维在显示技术路线上的重新抉择。但市场瞬息万变,为适应激烈的行
    华尔街科技眼 2025-03-05 20:03 147浏览
  • 服务器应用环境与客户需求PCIe 5.0高速接口技术的成熟驱动着生成式AI与高效能运算等相关应用蓬勃发展。在随着企业对服务器性能的要求日益严苛,服务器更新换代的周期也持续加快。在此背景下,白牌与DIY(Do It Yourself)服务器市场迎来了新的发展契机,但同时也面临着更趋复杂的技术挑战。传统上,白牌与DIY服务器以其高度客制化与成本效益优势受到市场青睐。然而,随着PCIe 5.0等高速技术的导入,服务器系统的复杂度大幅提升,对组装技术与组件兼容性也就提出更高的要求。举个简单的例子来说,P
    百佳泰测试实验室 2025-03-06 17:00 41浏览
  • 文/Leon编辑/cc孙聪颖2025年全国两会进行时,作为“十四五”规划收官之年,本届两会释放出坚定目标、稳中求进、以进促稳等信号。其中,企业家们的建议备受关注,关系到民营经济在2025年的走向。作为国内科技制造业的“老兵”,全国人大代表、TCL集团创始人及董事长李东生在本届两会中提出三份代表建议,包括《关于优化中国科技制造业融资环境的建议》、《关于加强AI深度伪造欺诈管理的建议》和《关于降低灵活就业人员社会保险参保门槛的建议》,表现出对科技制造、AI发展和劳动者保障方面的关注。会后,李东生接受
    华尔街科技眼 2025-03-06 19:41 34浏览
  • 在六西格玛项目中,团队的选择往往决定了最终的成败。合适的团队成员不仅能推动项目顺利进行,更能确保最终成果符合预期。因此,组建六西格玛团队时,必须挑选最合适的人才,确保他们具备必要的能力和特质。团队主管的关键特质每个精益六西格玛项目都需要一位主管来带领团队。他们不仅需要具备领导力,还要能够分析数据、制定策略,并与管理层和团队成员高效沟通。团队主管的核心职责包括:领导团队行动:能够激励成员,确保团队朝着既定目标前进。数据分析能力:精通数据处理和分析,能基于数据做出决策。沟通协调:能够在管理层和团队之
    优思学院 2025-03-06 12:51 97浏览
  • 多人同时共享相同无线网络,以下场景是否是您熟悉的日常?姐姐:「妈~我在房间在线上课,影音一直断断续续的怎么上课啊!」奶奶:「媳妇啊~我在在线追剧,影片一直卡卡的,实在让人生气!」除此之外,同时间有老公在跟客户开在线会议,还有弟弟在玩在线游戏,而妈妈自己其实也在客厅追剧,同时间加总起来,共有五个人同时使用这个网络!我们不论是在家里、咖啡厅、餐厅、商场或是公司,都会面临到周遭充斥着非常多的无线路由器(AP),若同时间每位使用者透过手机、平板或是笔电连接到相同的一个网络,可想而知网络上的壅塞及相互干扰
    百佳泰测试实验室 2025-03-06 16:50 36浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦