前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >[linux][perf]list过长导致CPU消耗过高的问题分析

[linux][perf]list过长导致CPU消耗过高的问题分析

作者头像
皮振伟
发布于 2019-12-15 11:18:07
发布于 2019-12-15 11:18:07
1.8K00
代码可运行
举报
文章被收录于专栏:皮振伟的专栏皮振伟的专栏
运行总次数:0
代码可运行

前言

某机器上网络出现时断时续的问题,网络的同事发现ovs进程的CPU消耗很高,硬件offload的规则下发卡住的问题。即通过netlink向内核发送消息卡住。

分析

perf分析热点函数

可见,CPU的主要消耗是在__tcf_chain_get函数和__mutex_lock上。

为什么锁消耗那么高

rtnetlink_rcv_msg函数(去掉参数检查等等,保留关键逻辑),通过这段逻辑我们可以知道,在执行每个netlink命令的时候,都会先执行锁操作,在执行具体的回调函数(即link->doit),再解锁的过程。

如果其中某一个回调函数执行时间过长,就会长时间占用锁,造成其他的link->doit回调函数block住更长的时间,那么锁的消耗也会更高。

再结合其他的代码逻辑可以发现,__tcf_chain_get函数就刚好在某一个回调函数的路径上。

Annotate __tcf_chain_get

分析上面的热点函数__tcf_chain_get

__tcf_chain_get比较长,理论上来说,一个cmp指令只要一个cycle即可,那么在这里为什么一个cmp指令使用了99.13%的CPU时间呢?可以从汇编中看到,需要取rax偏移0x18的地址上数值和esi进行比较。这里存在一次读取内存的操作,如果是链表的话,前后两个链表元素未必在内存上相邻,容易造成CPU的cache miss。

计算热点代码的路径

ffffffff8161ab40+1d= ffffffff8161ab5d

所以执行addr2line -e /usr/lib/debug/vmlinux-4.19 -a 0xffffffff8161ab5d

0xffffffff8161ab5d

可以得到/linux/net/sched/cls_api.c:268

可以看到268行是对chain->index和chain_index进行对比。

继续看tcf_chain结构体

index结构在tcf_chain结构体中偏移0x20,为什么反汇编的代码在0x18上?

结合上下文我们可以看到,使用list来遍历:

chain的地址是在list的地址-0x8,index的地址是在chain+0x20,那么index的地址相对于list的地址就是+0x18,计算chain的过程都被编译器优化掉了,只需要使用list的地址+0x18即可完成代码逻辑中的遍历过程。

综上,可以证实,__tcf_chain_get消耗过高的原因是:遍历list的过程中遇到了比较多的cache miss;遍历了太多的链表元素的导致的。

计算链表的长度

基于kprobe实现kmod,来dump出来链表的长度。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/uaccess.h>
#include <net/sch_generic.h>


static long filter_count(struct tcf_chain *chain)
{
  struct tcf_proto *tp;
  long count = 0;

  for (tp = rtnl_dereference(chain->filter_chain);
      tp; tp = rtnl_dereference(tp->next)) {
    count++;
  }
  return count;
}


static int kp_do_tcf_handler(struct kprobe *p, struct pt_regs *regs)
{
  struct tcf_block *block = (void __user *)regs->di;
  struct tcf_chain *chain;
  long count = 0;

  list_for_each_entry(chain, &block->chain_list, list) {
    count++;
  }

  printk("[always]count = %ld\n", count);

  return 0;
}

static struct kprobe kp = {
  .symbol_name  = "__tcf_chain_get",
  .pre_handler = kp_do_tcf_handler,
};

static int __init kprobe_init(void)
{
  int ret;

  ret = register_kprobe(&kp);
  if (ret < 0) {
    pr_err("register_kprobe failed, returned %d\n", ret);
    return ret;
  }

  pr_info("[probe-tcf]Planted kprobe at %p\n", kp.addr);
  return 0;
}

static void __exit kprobe_exit(void)
{
  unregister_kprobe(&kp);
  pr_info("[probe-unregistered]kprobe at %p unregistered\n", kp.addr);
}

module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");
MODULE_AUTHOR("pizhenwei pizhenwei@bytedance.com");

得到dump的结果

可以知道list的元素多少不等,有的不到100,有的到达了接近25W个元素。由此可以论证上面的猜测,链表元素太多。

找到哪个dev的链表元素过多

再进一步完善kprobe的逻辑,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/uaccess.h>
#include <net/sch_generic.h>

static long filter_count(struct tcf_chain *chain)
{
  struct tcf_proto *tp;
  long count = 0;

  for (tp = rtnl_dereference(chain->filter_chain);
      tp; tp = rtnl_dereference(tp->next)) {
    count++;
  }

  return count;
}

static int kp_do_tcf_handler(struct kprobe *p, struct pt_regs *regs)
{
  struct tcf_block *block = (void __user *)regs->di;
  struct tcf_chain *chain;
  long count = 0, tps;
  long tpc[4] = {0};  //绘制filter count分布图
  long ref[4] = {0};  //绘制refcnt的分布图
  long action_ref[4] = {0};  //绘制action_refcnt的分布图
  long actrefonly = 0;  //过滤出来action_refcnt等于refcnt的分布图

  list_for_each_entry(chain, &block->chain_list, list) {
    count++;

    tps = filter_count(chain);
    if (tps == 0)
      tpc[0]++;
    else if (tps > 0 && tps <= 1)
      tpc[1]++;
    else if (tps > 1 && tps <= 2)
      tpc[2]++;
    else
      tpc[3]++;

    if (chain->refcnt == 0)
      ref[0]++;
    else if (chain->refcnt > 0 && chain->refcnt <= 1)
      ref[1]++;
    else if (chain->refcnt > 1 && chain->refcnt <= 2)
      ref[2]++;
    else
      ref[3]++;

    if (chain->action_refcnt == 0)
      action_ref[0]++;
    else if (chain->action_refcnt > 0 && chain->action_refcnt <= 1)
      action_ref[1]++;
    else if (chain->action_refcnt > 1 && chain->action_refcnt <= 2)
      action_ref[2]++;
    else
      action_ref[3]++;


    if (chain->action_refcnt == chain->refcnt)
      actrefonly++;
  }


#if 1
  if (count < 1000)  //过滤链表元素少于1000的情况
    return 0;
#endif

  printk("[always]DEV %s\n", block→q→dev_queue→dev→name);  //打印出来异常的netdev的名字

  printk("[always][0]count = %ld\t", tpc[0]);
  printk("[always][1]count = %ld\t", tpc[1]);
  printk("[always][2]count = %ld\t", tpc[2]);
  printk("[always][4]count = %ld\n", tpc[3]);

  printk("[always][0]action_ref = %ld\t", action_ref[0]);
  printk("[always][1]action_ref = %ld\t", action_ref[1]);
  printk("[always][2]action_ref = %ld\t", action_ref[2]);
  printk("[always][4]action_ref = %ld\n", action_ref[3]);

  printk("[always][0]ref = %ld\t", ref[0]);
  printk("[always][1]ref = %ld\t", ref[1]);
  printk("[always][2]ref = %ld\t", ref[2]);
  printk("[always][4]ref = %ld\n", ref[3]);

  printk("[always]actrefonly = %ld\n", actrefonly);

  return 0;
}

static struct kprobe kp = {
  .symbol_name  = "__tcf_chain_get",
  .pre_handler = kp_do_tcf_handler,
};

static int __init kprobe_init(void)
{
  int ret;

  ret = register_kprobe(&kp);
  if (ret < 0) {
    pr_err("register_kprobe failed, returned %d\n", ret);
    return ret;
  }

  pr_info("[probe-tcf]Planted kprobe at %p\n", kp.addr);
  return 0;
}

static void __exit kprobe_exit(void)
{
  unregister_kprobe(&kp);
  pr_info("[probe-unregistered]kprobe at %p unregistered\n", kp.addr);
}

module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");
MODULE_AUTHOR("pizhenwei pizhenwei@bytedance.com");

得到dump出来的结果

至此,我们已经找到了vxlan_sys_4789上的元素比较多。

后面,经过网络同事的验证(通过tc命令dump出来结果),vxlan_sys_4789上的chain确实过多。脚本删除空的chain后,ovs的cpu消耗下降到10%以内,网络恢复正常。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-12-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 AlwaysGeek 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
kprobe分析内核kworker占用CPU 100%问题总结
利用linux kernel 动态追踪技术,排查问题本身就可能会变成一个非常有趣的过程,让我们遇到线上的诡异问题就感到兴奋,就仿佛好不容易又逮着机会,可以去解一道迷人的谜题。
全栈程序员站长
2022/11/08
2.8K0
[linux][kprobe]谁动了我的文件---使用kprobe找到目标进程
问题场景: 云计算IaaS平台上,经常使用libvirt+qemu-kvm做基础平台。libvirt会在/etc/libvirt/qemu/目录下,保存很多份qemu的配置文件,如ubuntu.xml。 作者发现其中的配置文件会在特定的场景下被修改,却不知道哪个进程是凶手。为了找到凶手,作者写下了这个debug工具。 代码分析: 代码路径:https://github.com/pacepi/whotouchmyfile #include <linux/kernel.h> #include <linux/mo
皮振伟
2018/04/09
2.4K0
Linux 内核动态追踪技术的实现
前言:之前的文章介绍了基于 tracepoint 静态追踪技术的实现,本文再介绍基于 kprobe 的动态追踪即使的实现。同样,动态追踪也是排查问题的利器。
theanarkh
2021/11/19
8310
深入探究Linux Kprobe机制
kprobe机制用于在内核中动态添加一些探测点,可以满足一些调试需求。本文主要探寻kprobe的执行路径,也就是说如何trap到kprobe,以及如何回到原路径继续执行。
Linux阅码场
2020/12/14
1.6K0
Kernel调试追踪技术之 Kprobe on ARM64
kprobe 是一种动态调试机制,用于debugging,动态跟踪,性能分析,动态修改内核行为等,2004年由IBM发布,是名为Dprobes工具集的底层实现机制[1][2],2005年合入Linux kernel。probe的含义是像一个探针,可以不修改分析对象源码的情况下,获取Kernel的运行时信息。
233333
2024/04/03
4520
Kernel调试追踪技术之 Kprobe on ARM64
Linux 内核监控在 Android 攻防中的应用
在日常分析外部软件时,遇到的反调试/反注入防护已经越来越多,之前使用的基于 frida 的轻量级沙盒已经无法满足这类攻防水位的需要,因此需要有一种更加深入且通用的方式来对 APP 进行全面的监测和绕过。本文即为对这类方案的一些探索和实践。
evilpan
2023/02/12
3.4K0
Linux 内核监控在 Android 攻防中的应用
Linux内核调试技术——kprobe使用与实现
Linux kprobes调试技术是内核开发者们专门为了便于跟踪内核函数执行状态所设计的一种轻量级内核调试技术。利用kprobes技术,内核开发人员可以在内核的绝大多数指定函数中动态的插入探测点来收集所需的调试状态信息而基本不影响内核原有的执行流程。kprobes技术目前提供了3种探测手段:kprobe、jprobe和kretprobe,其中jprobe和kretprobe是基于kprobe实现的,他们分别应用于不同的探测场景中。本文首先简单描述这3种探测技术的原理与区别,然后主要围绕其中的kprobe技术进行分析并给出一个简单的实例介绍如何利用kprobe进行内核函数探测,最后分析kprobe的实现过程(jprobe和kretprobe会在后续的博文中进行分析)。
嵌入式Linux内核
2022/09/24
2.7K0
Linux内核调试技术——kprobe使用与实现
万字长文解读 Linux 内核追踪机制
Linux 存在众多 tracing tools,比如 ftrace、perf,他们可用于内核的调试、提高内核的可观测性。众多的工具也意味着繁杂的概念,诸如 tracepoint、trace events、kprobe、eBPF 等,甚至让人搞不清楚他们到底是干什么的。本文尝试理清这些概念。
刘盼
2023/08/22
2.6K0
万字长文解读 Linux 内核追踪机制
Linux内核调试技术——kprobe使用与实现
Linux kprobes调试技术是内核开发者们专门为了便于跟踪内核函数执行状态所设计的一种轻量级内核调试技术。利用kprobes技术,内核开发人员可以在内核的绝大多数指定函数中动态的插入探测点来收集所需的调试状态信息而基本不影响内核原有的执行流程。kprobes技术目前提供了3种探测手段:kprobe、jprobe和kretprobe,其中jprobe和kretprobe是基于kprobe实现的,他们分别应用于不同的探测场景中。本文首先简单描述这3种探测技术的原理与区别,然后主要围绕其中的kprobe技术进行分析并给出一个简单的实例介绍如何利用kprobe进行内核函数探测,最后分析kprobe的实现过程(jprobe和kretprobe会在后续的博文中进行分析)。
233333
2021/09/08
6.1K0
内核级防篡改
网络攻击者通常会利用被攻击网站中存在的漏洞,通过在网页中植入非法暗链对网页内容进行篡改等方式,进行非法牟利或者恶意商业攻击等活动。网页被恶意篡改会影响用户正常访问网页内容,还可能会导致严重的经济损失、品牌损失甚至是政治风险。
Laikee
2022/04/25
2.2K1
内核级防篡改
Linux内核跟踪:ftrace hook入门手册(上)
ftrace(FunctionTracer)是Linux内核的一个跟踪框架,它从2008年10月9日发布的内核版本2.6.27开始并入Linux内核主线[1]。官方文档[2]中的描述大致翻译如下:
绿盟科技研究通讯
2022/06/06
3K0
Linux内核跟踪:ftrace hook入门手册(上)
【调试】kprobes(一)基本概念
开发人员在内核或者模块的调试过程中,往往会需要要知道其中的一些函数有无被调用、何时被调用、执行是否正确以及函数的入参和返回值是什么等等。
嵌入式与Linux那些事
2023/03/24
1.2K0
【调试】kprobes(一)基本概念
使用jprobe建设镜面层叠的原则和见解
忽然想起的回忆,那是2007上周五在冬季,我看我的老湿调试Linux堆IP层,只看到他改变路由查找的逻辑,然后直接make install上的立竿见影的效果有点,我只知道,,这种逻辑必须再次更改编译内核。再一次,他没有编译,就像刚才编译的文件…时又无聊的工作阻碍了我对Linux内核的探索进度,直到今天,我依旧对编译内核有相当的恐惧,不怕出错,而是怕磁盘空间不够,initrd的组装拆解之类,太繁琐了。我之所以知道2007年的那天是周五,是由于第二天我要加班。没有谁逼我。我自愿的,由于我想知道师父是怎么做到不又一次编译内核就能改变非模块的内核代码处理逻辑的。第二天的收获非常多,不但知道了他使用了“镜像协议栈”。还额外赚了一天的加班费。我还记得周六加完班我和老婆去吃了一家叫做石工坊的羊排火锅。人家赠送了一仅仅绿色的兔子玩偶。
全栈程序员站长
2022/07/06
7410
Linux内核通知链机制的原理及实现
一、概念: 大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子 系统,Linux内核提供了通知链的机制。通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。 通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。所以对于通知链表来说有一个通知 方与一个接收方。在通知这个事件时所运行的函数由被通知方决定,实际上也即是被通
李海彬
2018/03/23
2.1K0
Linux内核通知链(Notifier)
在linux内核中,各个子系统之间有很强的相互关系,某些子系统可能对其他子系统产生的事件比较感兴趣。因此内核引入了notifier机制,当然了notifier机制只能用在内核子系统之间,不能用在内核与应用层之间。比如当系统suspend的时候,就会使用到notifier机制来通知系统的内核线程进行suspend。
DragonKingZhu
2020/03/24
2.7K0
《Essential Linux Dev
wq = create_singlethread_workqueue("mydrv");
py3study
2020/01/08
2.4K0
Linux Rootkit如何避开内核检测的
如果我们想注入一个Rootkit到内核,同时不想被侦测到,那么我们需要做的是精妙的隐藏,并保持低调静悄悄,这个话题我已经谈过了,诸如进程摘链,TCP链接摘链潜伏等等,详情参见:https://blog.csdn.net/dog250/article/details/105371830
Linux阅码场
2020/05/15
1.4K0
firefly-rk3288开发板Linux驱动——AT24C02 E2PROM驱动
I2C核心提供了I2C总线驱动和设备驱动注册、注销函数,I2C通信函数、探测设备、检测设备地址函数等。
知否知否应是绿肥红瘦
2025/02/19
1320
tracepoint类型的ebpf程序是如何被执行的
本文基于libbpf实现的ebpf例子介绍tracepoint类型ebpf程序调用流程,内核实现以5.4版本为例进行介绍。
cdh
2023/09/11
1.8K0
android 休眠唤醒机制分析(一) — wake_lock【转】
Android的休眠唤醒主要基于wake_lock机制,只要系统中存在任一有效的wake_lock,系统就不能进入深度休眠,但可以进行设备的浅度休眠操作。wake_lock一般在关闭lcd、tp但系统仍然需要正常运行的情况下使用,比如听歌、传输很大的文件等。本文主要分析driver层wake_lock的实现。
233333
2018/12/14
3.5K0
相关推荐
kprobe分析内核kworker占用CPU 100%问题总结
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验