Linux上使用netlink机制监控新创建的进程

问题

有些进程出现频率高,但是它的生命周期很短,比如 在1s之内就消失了,我曾经遇到的一种场景就是,系统负载有点偏高,其中一个原因是有不少是ps、awk等这样的命令进程过多,通过传统的top命令上,很难看到 或者 说就算看到了也是一闪而过。而且这种情况往往是其父进程,如一些python/shell脚本里面经常在调用ps、awk等小命令,我是需要讲这些父进程给找出来才方便去做优化。
到这里,就引出了一个需求,如何实时的监控Linux上新产生的进程,需要打印出 新进程PID、命令行、父进程PID等信息。

解决方案

要对进程实时监控,最好的方式肯定是直接和内核通讯,能够注册一个监听器,每当有新进程创建就立马收到一个事件。在Linux上有一种在用户态和内核态通信的方式叫做Netlink;而 Kernel connector 是一种 Netlink ,它的 Netlink 协议号是 NETLINK_CONNECTOR, 这种特性可以用来实时获取进程启动和退出的事件。Kernel connector 能够获取的事件在用户空间的 /usr/include/linux/cn_proc.h 文件中定义。我主要关注PROC_EVENT_FORK, PROC_EVENT_EXEC, PROC_EVENT_EXIT就可以获取进程启动和退出事件。

基于netlink的通信中,有两种可能的情形会导致消息丢失:
1. 内存耗尽,没有足够多的内存分配给消息
2. 缓存复写,接收队列中没有空间存储消息,这在内核空间和用户空间之间通信时可能会发生
而缓存复写在以下情况很可能会发生:
* 内核子系统以一个恒定的速度发送netlink消息,但是用户态监听者处理过慢
* 用户存储消息的空间过小

注意在监听后的事件处理程序中,不要耗时过长(或者 最好把同步改为异步处理)。

这里可以看到,监听了进程创建的事件,但是它无法直接获取进程的名称,而如果没有进程名称,就不方便做一些规格来对进程进行处理。这里推荐使用 /proc/$pid/cmdline 接口去获取进程名(及命令行参数),但同时也需要注意 不能使用 cat /proc/$pid/cmdline 这样的命令去处理,因为用shell命令处理又产生了新的进程,会触发监听事件,有会执行cat命令,这样就会死循环了。

这样使用netlink实现对新进程的监听的C语言示例:
https://github.com/smilejay/c-cpp/blob/master/c2023/new_proc_monitor.c

效果如下:

$sudo ./pmon_2
set mcast listen ok
fork: parent tid=33084 pid=32986 -> child tid=106482 pid=106482 cmd:java
exec: tid=106482 pid=106482 cmd:sh
fork: parent tid=106482 pid=106482 -> child tid=106483 pid=106483 cmd:sh
fork: parent tid=106482 pid=106482 -> child tid=106484 pid=106484 cmd:sh
fork: parent tid=106482 pid=106482 -> child tid=106485 pid=106485 cmd:sh
exec: tid=106483 pid=106483 cmd:ps
exec: tid=106484 pid=106484 cmd:grep
exec: tid=106485 pid=106485 cmd:awk
unhandled proc event
unhandled proc event
unhandled proc event
unhandled proc event
fork: parent tid=0 pid=0 -> child tid=106486 pid=1 cmd:/usr/lib/systemd/systemd
unhandled proc event
fork: parent tid=33084 pid=32986 -> child tid=106487 pid=106487 cmd:java
exec: tid=106487 pid=106487 cmd:sh
fork: parent tid=106487 pid=106487 -> child tid=106488 pid=106488 cmd:sh
fork: parent tid=106487 pid=106487 -> child tid=106489 pid=106489 cmd:sh
fork: parent tid=106487 pid=106487 -> child tid=106490 pid=106490 cmd:sh
exec: tid=106488 pid=106488 cmd:ps
exec: tid=106489 pid=106489 cmd:grep
exec: tid=106490 pid=106490 cmd:awk
unhandled proc event
unhandled proc event

果然各种新进程全部记录在案,遇到这类系统问题,就更容易详细分析了。

另外在Go语言中使用netlink也有一些例子可以参考:
https://github.com/vishvananda/netlink/blob/main/proc_event_linux.go
https://asphaltt.github.io/post/netlink-and-go/

其他方案1

大神 Brendan Gregg的性能工具集perf-tools中的,基于fTrace的工具,叫做:execsnoop 。见:https://github.com/brendangregg/perf-tools/blob/master/execsnoop
https://www.brendangregg.com/blog/2014-07-28/execsnoop-for-linux.html
当然,随着较新的Linux kernel对eBPF的支持后,现在比较推荐使用:BCC工具集中的execsnoop,见:https://github.com/iovisor/bcc/blob/master/tools/execsnoop.py
另外,bcc这个真是性能宝库啊,后面可以好好研究。

其他方案2

使用forkstat这个工具,forkstat是一个记录进程fork()、exec()、exit()、coredump和进程名称更改活动的程序。它对于监视系统行为和跟踪派生进程并可能滥用系统的恶意进程非常有用。
forkstat – a tool to show process fork/exec/exit activity。

源代码:https://github.com/ColinIanKing/forkstat

forkstat 还是使用netlink的机制,效果如下:

$ sudo forkstat -e exec -D 3
Time     Event     PID Info   Duration Process
14:28:09 exec  1849907                 head -v -n 8 /proc/meminfo
14:28:09 exec  1849908                 head -v -n 2 /proc/stat /proc/version /proc/uptime /proc/loadavg /proc/sys/fs/file-nr /proc/sys/kernel/hostname
14:28:09 exec  1849909                 tail -v -n 16 /proc/net/dev
14:28:09 exec  1849910                 df -l
14:28:09 exec  1849911                 who
14:28:09 exec  1849912                 sleep 1
14:28:10 exec  1849913                 head -v -n 8 /proc/meminfo
14:28:10 exec  1849914                 head -v -n 2 /proc/stat /proc/version /proc/uptime /proc/loadavg /proc/sys/fs/file-nr /proc/sys/kernel/hostname
14:28:10 exec  1849915                 tail -v -n 16 /proc/net/dev
14:28:10 exec  1849916                 df -l
14:28:10 exec  1849917                 who
14:28:10 exec  1849918                 sleep 1
14:28:11 exec  1849919                 head -v -n 8 /proc/meminfo
14:28:11 exec  1849920                 head -v -n 2 /proc/stat /proc/version /proc/uptime /proc/loadavg /proc/sys/fs/file-nr /proc/sys/kernel/hostname
14:28:11 exec  1849921                 tail -v -n 16 /proc/net/dev
14:28:12 exec  1849922                 df -l
14:28:12 exec  1849923                 who
14:28:12 exec  1849924                 sleep 1

参考资料

  1. forkstat 和 netlink connector 介绍:https://betheme.net/xiaochengxu/100935.html
  2. Linux和Windows上监控新进程创建的方式: https://devpress.csdn.net/cicd/62eb776620df032da732b454.html
  3. 一篇netlink的详细介绍:https://lishiwen4.github.io/network/netlink
  4. Linux 进程实时监控-netlink:https://juejin.cn/post/7113728552561803272
  5. 再一篇netlink文章:https://betheme.net/xiaochengxu/100935.html
  6. 一个netlink范例代码来源:https://bewareofgeek.livejournal.com/2945.html
  7. Brendan Gregg大神的perf-tools: https://github.com/brendangregg/perf-tools
  8. 各种eBPF基础学习:https://asphaltt.github.io/post/
  9. Linux性能监控必须学习的: BCC – Tools for BPF-based Linux IO analysis, networking, monitoring, and more. https://github.com/iovisor/bcc

master

Stay hungry, stay foolish.

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

*