Linux 内核静态追踪技术的实现

本文简单分享一下内核的静态追踪技术的实现。追踪,其实就是收集代码在执行时的一些信息,以便协助排查问题。 2021-11-14 07:29:55 Linux 内核静态追踪Linux 系统 Linux 系统查看磁盘可用空间的五个命令 工作中,经常会遇到磁盘爆满的情况,尤其是一台服务器运行了 N 年之后,里面会充满各种各样垃圾文件,比如:编译产生的中间文件、打包的镜像文件、日志文件,等等。 2021-11-14 05:00:40 Linux 命令 Windows 11预览版KB5007262发布:文件资源管理器“迷之卡死”终于得到修复 微软为处在Beta和Release Preview Insider频道的用户推送了build 22000.346 (KB5007262)更新。此次更新解决了Windows 11存在的大量问题,其中就包含了数个可能导致文件资源管理器卡死或无法正常工作的bug。 2021-11-13 09:34:01 Windows 11操作系统微软 WPS新增支持重磅功能!告诉你XLOOKUP有多强 不久前,WPS官微发布了一条消息,说是自即日起WPS开始正式支持XLOOKUP函数。其实能让WPS“激动”自然是有些道理,理由就是这个XLOOKUP实在太强了! 2021-11-13 07:33:08 WPSXLOOKUP办公软件 Windows 10系统怎么进行蓝牙连接?Windows 10系统蓝牙链接操作步骤 win10系统适用蓝牙作用,因而许多蓝牙机器设备例如蓝牙电脑鼠标,蓝牙手机耳机,蓝牙音响等都能够连接win10蓝牙作用应用。但是有一些网民针对有关操作不了解,不知道如何连接win10蓝牙。下面我就教下大伙儿连接win10蓝牙应用的方法。 2021-11-12 23:44:28 Windows 10Windows微软 你知道吗?Windows 11的这11个功能最糟糕 如果你选择迁移到Windows 11,无论是通过升级还是全新安装,你都会注意到几个明显的烦恼,包括让你额外点击一次的上下文菜单、蹒跚的任务栏和让你更难从Edge切换的默认浏览器菜单。 2021-11-12 23:41:27 Windows 11Windows微软 微软Windows 11封杀第三方浏览器工具:默认打开就得使用Edge 别的不行 Win11中更改默认浏览器已经很麻烦了,现在微软在Win11 Build 22494中进一步封杀了第三方工具Edgedeflector,导致很多人打开网址不能用自己默认的浏览器了。 2021-11-12 21:18:46 Windows 11操作系统微软 在苹果 M1 上运行 Linux 虚拟机变得容易了 Canonical 使用户可以借助 Multipass(一个免费的虚拟机程序)在苹果 M1 上运行 Linux 虚拟机。 2021-11-12 21:13:21 苹果苹果 M1Linux虚拟机 微软 Windows 11 预览版已屏蔽 Edge Deflector,链接重定向修改软件失效 IT之家 11 月 12 日消息,据 MSPoweruser 报道,几年来,微软一直在争取迫使 Windows 用户使用 Edge 浏览器,经常地迫使链接在浏览器中使用 microsoft-edge://协议打开。

本文简单分享一下内核的静态追踪技术的实现。追踪,其实就是收集代码在执行时的一些信息,以便协助排查问题。

[[434855]]

前言:最近在探索 Node.js 调试和诊断方向的内容,因为 Node.js 提供的能力有时候可能无法解决问题,比如堆内存没有变化,但是 rss 一直上涨。所以需要深入一点去了解更多的排查问题方式。而这些方向往往都涉及到底层的东西,所以就自然需要去了解内核提供的一些技术,内核提供的能力,经过多年的发展,可谓是百花齐放,而且非常复杂。本文简单分享一下内核的静态追踪技术的实现。追踪,其实就是收集代码在执行时的一些信息,以便协助排查问题。

1 Tracepoint

Tracepoints 是一种静态插桩的技术,实现虽然复杂,但是概念上比较简单。比如我们打日志的时候,就类似这种情况,我们在业务代码里,写了很多 log 用来记录进程在运行时的信息。Tracepoints 则是内核提供的一种基于钩子的插桩技术。不过和打日志不一样的是,我们想在哪里打就在哪里加对应的代码,而 Tracepoints 则几乎是依赖于内核决定哪里可以插桩,说几乎是因为我们也可以写内核模块注册到内核来通知插桩点。下面来通过一个例子看一下 Tracepoint 的使用和实现(例子来自内核文档 tracepoints.rst)。分析之前先看一下两个非常重要的宏。第一个是 DECLARE_TRACE。

  1. #defineDECLARE_TRACE(name,proto,args)\
  2. __DECLARE_TRACE(name,PARAMS(proto),PARAMS(args),\
  3. cpu_online(raw_smp_processor_id()),\
  4. PARAMS(void*__data,proto),\
  5. PARAMS(__data,args))

我们只需要关注主体的实现,而不需要关注参数,继续展开。

  1. #define__DECLARE_TRACE(name,proto,args,cond,data_proto,data_args)\
  2. externstructtracepoint__tracepoint_##name;\
  3. //执行钩子函数
  4. staticinlinevoidtrace_##name(proto)\
  5. {\
  6. if(static_key_false(&__tracepoint_##name.key))\
  7. __DO_TRACE(&__tracepoint_##name,\
  8. TP_PROTO(data_proto),\
  9. TP_ARGS(data_args),\
  10. TP_CONDITION(cond),0);\
  11. }\
  12. //注册钩子函数
  13. staticinlineint\
  14. register_trace_##name(void(*probe)(data_proto),void*data)\
  15. {\
  16. returntracepoint_probe_register(&__tracepoint_##name,\
  17. (void*)probe,data);\
  18. }\
  19. //注销钩子函数
  20. staticinlineint\
  21. unregister_trace_##name(void(*probe)(data_proto),void*data)\
  22. {\
  23. returntracepoint_probe_unregister(&__tracepoint_##name,\
  24. (void*)probe,data);\
  25. }\
  26. staticinlinebool\
  27. trace_##name##_enabled(void)\
  28. {\
  29. returnstatic_key_false(&__tracepoint_##name.key);\
  30. }

__DECLARE_TRACE 主要是实现了几个函数,我们只需要关注注册钩子和执行钩子函数(格式是 register_trace_${yourname} 和 trace_${yourame})。接下来看第二个宏 DEFINE_TRACE。

  1. #defineDEFINE_TRACE_FN(name,reg,unreg)\
  2. structtracepoint__tracepoint_##name#defineDEFINE_TRACE(name)\
  3. DEFINE_TRACE_FN(name,NULL,NULL);

我省略了一些代码,DEFINE_TRACE 主要是定义了一个 tracepoint 结构体。了解了两个宏之后,来看一下如何使用 Tracepoint。

1.1 使用

include/trace/events/subsys.h

  1. #include<linux/tracepoint.h>DECLARE_TRACE(subsys_eventname,
  2. TP_PROTO(intfirstarg,structtask_struct*p),
  3. TP_ARGS(firstarg,p));

首先在头文件里通过 DECLARE_TRACE 宏定义了一系列函数。subsys/file.c

  1. #include<trace/events/subsys.h>
  2. DEFINE_TRACE(subsys_eventname);voidsomefct(void){
  3. ...
  4. trace_subsys_eventname(arg,task);
  5. ...
  6. }
  7. //实现自己的钩子函数并注册到内核
  8. voidcallback(...){}
  9. register_trace_subsys_eventname(callback);

然后在实现文件里通过 DEFINE_TRACE 定义一个 tracepoint 结构体。接着调用 register_trace_subsys_eventname 函数把自定义的钩子函数注册到内核,然后在需要收集信息的地方调用处理钩子的函数 trace_subsys_eventname。

1.2 实现

了解了使用之后,接下来看看实现。首先看一下注册钩子函数。

  1. inttracepoint_probe_register(structtracepoint*tp,void*probe,void*data){
  2. returntracepoint_probe_register_prio(tp,probe,data,TRACEPOINT_DEFAULT_PRIO);
  3. }
  4. inttracepoint_probe_register_prio(structtracepoint*tp,void*probe,
  5. void*data,intprio){
  6. structtracepoint_functp_func;
  7. intret;
  8. mutex_lock(&tracepoints_mutex);
  9. tp_func.func=probe;
  10. tp_func.data=data;
  11. tp_func.prio=prio;
  12. ret=tracepoint_add_func(tp,&tp_func,prio);
  13. mutex_unlock(&tracepoints_mutex);
  14. returnret;
  15. }

tracepoint_probe_register_prio 中定义了一个 tracepoint_func 结构体用于表示钩子信息,然后调用 tracepoint_add_func,其中 tp 就刚才自定义的 tracepoint 结构体。

  1. staticinttracepoint_add_func(structtracepoint*tp,structtracepoint_func*func,intprio){
  2. structtracepoint_func*old,*tp_funcs;
  3. intret;
  4. //拿到钩子列表
  5. tp_funcs=rcu_dereference_protected(tp->funcs,lockdep_is_held(&tracepoints_mutex));
  6. //插入新的钩子到列表
  7. old=func_add(&tp_funcs,func,prio);
  8. rcu_assign_pointer(tp->funcs,tp_funcs);
  9. return0;}staticstructtracepoint_func*func_add(structtracepoint_func**funcs,structtracepoint_func*tp_func,
  10. intprio){
  11. structtracepoint_func*new;
  12. intnr_probes=0;
  13. intpos=-1;
  14. /*+2:onefornewprobe,oneforNULLfunc*/
  15. new=allocate_probes(nr_probes+2);
  16. pos=0;
  17. new[pos]=*tp_func;
  18. new[nr_probes+1].func=NULL;
  19. *funcs=new;
  20. }

注册函数的逻辑其实就是往自定义的结构体的队列里插入一个新的节点。接下来再看一下处理钩子的逻辑。

  1. #define__DO_TRACE(tp,proto,args,cond,rcuidle)\
  2. do{\
  3. structtracepoint_func*it_func_ptr;\
  4. void*it_func;\
  5. void*__data;\
  6. int__maybe_unused__idx=0;\
  7. //拿到队列
  8. it_func_ptr=rcu_dereference_raw((tp)->funcs);\
  9. //非空则执行里面的节点的回调
  10. if(it_func_ptr){\
  11. do{\
  12. it_func=(it_func_ptr)->func;\
  13. __data=(it_func_ptr)->data;\
  14. ((void(*)(proto))(it_func))(args);\
  15. }while((++it_func_ptr)->func);\
  16. }\
  17. }while(0)

逻辑上和我们在应用层的类似。在执行钩子,也就是我们的回调时,我们可以通过内核接口把信息写到 ring buffer,然后应用层可以通过 debugfs 获取到这个信息。

2 trace event

有了 Tracepoint 机制后,我们就可以写模块加载到内核中实现自己的插桩点。但是内核也为我们内置提供了非常多的插桩点。具体是通过 trace event 来实现的。下面看一个例子。

  1. #defineTRACE_EVENT(name,proto,args,struct,assign,print)\
  2. DECLARE_TRACE(name,PARAMS(proto),PARAMS(args))TRACE_EVENT(consume_skb,
  3. TP_PROTO(structsk_buff*skb),
  4. TP_ARGS(skb),
  5. TP_STRUCT__entry(
  6. __field(void*,skbaddr)
  7. ),
  8. TP_fast_assign(
  9. __entry->skbaddr=skb;
  10. ),
  11. TP_printk("skbaddr=%p",__entry->skbaddr));

上面定义了一个宏 TRACE_EVENT,它本质上是对 DECLARE_TRACE 的封装,所以这里是定义了一系列的函数(注册钩子、处理钩子)。然后在 consume_skb 函数中处理了注册的钩子。

  1. voidconsume_skb(structsk_buff*skb){
  2. trace_consume_skb(skb);
  3. __kfree_skb(skb);
  4. }

3. 总结

内核提供了非常丰富但是也非常复杂的机制,从而用户可以通过内核的能力获取到更底层的数据,用以排查问题和做性能优化。我们可以看到插桩的这种机制是一种静态的机制,我们通常需要依赖当前版本的内核所支持的桩,从而获得对应的信息,但其实内核也提供了动态追踪的能力,可以实现热插拔获取信息的能力。总的来说,Linux 下的追踪技术多种多样,虽然非常复杂,但是上层也提供了各种更方便的工具,这些能力是我们深入排查问题的利器。

©本文为清一色官方代发,观点仅代表作者本人,与清一色无关。清一色对文中陈述、观点判断保持中立,不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。本文不作为投资理财建议,请读者仅作参考,并请自行承担全部责任。文中部分文字/图片/视频/音频等来源于网络,如侵犯到著作权人的权利,请与我们联系(微信/QQ:1074760229)。转载请注明出处:清一色财经

(0)
打赏 微信扫码打赏 微信扫码打赏 支付宝扫码打赏 支付宝扫码打赏
清一色的头像清一色管理团队
上一篇 2023年5月8日 23:30
下一篇 2023年5月8日 23:31

相关推荐

发表评论

登录后才能评论

联系我们

在线咨询:1643011589-QQbutton

手机:13798586780

QQ/微信:1074760229

QQ群:551893940

工作时间:工作日9:00-18:00,节假日休息

关注微信