Linux:利用内核日志记录系统启动时产生的进程树

16 篇文章 0 订阅

前言

之前项目中遇到过一个bug,bug产生的原因是某个程序在两个不同的启动脚本中被同时启动了两次,系统中出现了两个实例。这个程序在代码内部没有保证单实例,靠shell脚本的pidof(1)命令保证单实例,然而因为两个启动脚本的启动时间太过接近,pidof(1)没能起作用,导致程序被启动了两次。

出了这个bug后就想着需要梳理一下系统在启动时都运行了哪些脚本,这个系统“传承”了估计有七八年,启动脚本粗略估计有几十个了,这几十个脚本里面必然有可以合并或者精简的。一开始人肉分析,发现难度有点大,有些脚本是rc脚本,有些脚本在rc脚本中被启动,有些脚本被网卡配置脚本启动,有些脚本被Xserver启动脚本启动,甚至有些脚本放在了窗口管理器启动脚本中启动,还有些脚本虽然在磁盘上躺着,但已经被另外的启动脚本取代了,但根本没有被启动。

所以有没有现成的工具能够记录系统启动时都启动了哪些进程呢,我一开始想到了用auditd,之前我在另外一篇博客:linux下监控shell脚本或可执行程序启动过的子进程中提到过auditd,它在我的Ubuntu上工作得很正常(不过会拖慢系统速度),但在项目的系统里面无法正常工作,很容易把系统搞死,在经过几个小时的尝试以后,我放弃了这种方法。

经过一番思考后,我决定使用最笨最直接的方法:printk大法。既然有内核源码,何不直接修改内核,在内核创建进程的时候打印出来呢?

日志生成流程和结果可视化代码的Github仓库:Tasktree

环境

我使用的“发行版”是之前编的一个lfs,内核版本是5.2.8

修改内核

首先需要指出,我在文章标题写的是“进程树”,这个“进程”指的是Linux内核里面的“轻量级进程”(LWP),而不是操作系统课本里的那个“进程”的概念。在Linux内核中并不会区分进程或者线程,只有LWP,使用task_struct结构体保存。

总共有3个需要修改的源文件:kernel/fork.c、fs/exec.c和kernel/exit.c,只要能够获取一个每一个进程fork/exec/exit的时间,我们就能够还原出整颗进程树。

插入的printk主要需要记录进程号和进程名的信息,为了方便以后将内核线程隐藏,我还在fork.c的printk中记录了p->flags & PF_KTHREAD的值。

kernel/fork.c

copy_process中,在return之前插入一个printk

...
    trace_task_newtask(p, clone_flags);
    uprobe_copy_process(p, clone_flags);
    printk(KERN_ERR "FORK|%d|%s|=>|%d|%u", current->pid, current->comm, p->pid, p->flags & PF_KTHREAD); // Inserted here!
    return p;
...

fs/exec.c

__set_task_comm中,在函数一开始插入一个printk

void __set_task_comm(struct task_struct *tsk, const char *buf, bool exec)
{       
    printk(KERN_ERR "EXEC|%d|%s|=|%s", tsk->pid, tsk->comm, buf); // Inserted here!
    task_lock(tsk);
    trace_task_rename(tsk, buf);
    strlcpy(tsk->comm, buf, sizeof(tsk->comm));
    task_unlock(tsk);
    perf_event_comm(tsk, exec);
}

kernel/exit.c

do_exit中,在函数一开始插入一个printk

void __noreturn do_exit(long code)
{       
    printk(KERN_ERR "EXIT|%d|%s", current->pid, current->comm); // Inserted here!
    struct task_struct *tsk = current;
    int group_dead;
    profile_task_exit(tsk);
...

日志输出

将修改后的内核编译安装完毕后重新启动系统,我们就能在/var/log/kern.log或者dmesg(1)命令的输出中看到我们插入的日志了:

[    4.070211] FORK|170|S05modules|=>|172|0
[    4.082378] EXEC|172|S05modules|=|egrep
[    4.092978] EXEC|172|egrep|=|grep
[    4.098429] EXIT|172|grep
[    4.099345] EXIT|170|S05modules
[    4.099777] FORK|130|rc|=>|173|0
[    4.100872] EXEC|173|rc|=|S08localnet
[    4.102791] FORK|173|S08localnet|=>|174|0
[    4.103201] EXEC|174|S08localnet|=|stty
[    4.104313] EXIT|174|stty
[    4.108745] FORK|173|S08localnet|=>|175|0
[    4.123296] EXEC|175|S08localnet|=|cat
[    4.125846] EXIT|175|cat
[    4.126549] FORK|173|S08localnet|=>|176|0

进程树

使用Tasktree将日志还原成进程树,会得到如下输出:

[0] idle(living)
 \_ [1] swapper/0 -> [1] init(living)
 |                    \_ [129] init
 |                    |   \_ [130] init -> [130] rc
 |                    |   |                 \_ [133] rc -> [133] stty
 |                    |   |                 \_ [134] rc -> [134] dmesg
 |                    |   |                 \_ [135] rc
 |                    |   |                 |   \_ [136] rc -> [136] ls
 |                    |   |                 \_ [137] rc -> [137] S00mountvirtfs
 |                    |   |                 |               \_ [138] S00mountvirtfs -> [138] stty
 ...

" -> " 表示一个exec 调用。

" \_ " 表示一个fork 调用。

这个输出样式是仿照ps(1)做的,但是ps(1)输出不了已经退出的进程,也记录不了exec调用。

时间线

由于日志都是有时间戳的,除了能生成进程树外,还能画出进程fork/exec/exit的时间线:

Timeline

结语

以上就是使用内核日志来还原出进程树的流程了。修改内核听起来虽有些麻烦,但是比使用各种文档残缺可用性难以保障的工具的心智负担要低得多。如果你有更好的方式或工具,请一定要指教一下。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《深入Linux设备驱动程序内核机制》是一本非常经典的Linux设备驱动程序内核开发方面的专业书籍。该书由深入浅出的方式,全面介绍了Linux设备驱动程序的基本知识、内核框架、字符设备驱动程序、块设备驱动程序以及网络设备驱动程序等内容。 该书首先详细介绍了Linux操作系统的内核架构和设备驱动程序的基本概念,让读者对Linux内核的组成、系统调用、进程管理等有一个清晰的了解。接着,该书介绍了设备驱动程序的开发流程和编写规范,并重点讲解了字符设备驱动程序的开发方法。通过具体的代码实例,读者可以深入了解字符设备的注册、读写操作以及地址映射等关键步骤。 此外,该书还涵盖了块设备驱动程序和网络设备驱动程序等领域的知识。块设备驱动程序的开发涉及磁盘操作、缓冲区管理等内容,而网络设备驱动程序的开发则包括套接字的初始化、数据传输等方面。通过学习这些内容,读者不仅可以掌握Linux设备驱动程序的内核机制,还能够应对更加复杂的设备驱动开发工作。 综上所述,《深入Linux设备驱动程序内核机制》是一本非常实用和权威的Linux设备驱动程序开发方面的书籍。通过阅读本书,读者可以系统地学习Linux设备驱动的原理和开发方法,提升自己在Linux内核开发领域的技能和水平。无论是对于初学者还是有一定经验的开发者来说,都是一本不可或缺的参考书。 ### 回答2: "深入Linux设备驱动程序内核机制"是一本非常重要的图书,主要介绍了Linux操作系统中的设备驱动程序开发原理和实践技巧。这本书非常适合那些对Linux设备驱动程序开发感兴趣的人阅读。 首先,这本书详细讲解了Linux设备驱动程序的内核机制。它介绍了设备驱动程序的基本概念、内核模块的加载和卸载、驱动程序的注册和注销、设备的访问和控制等重要知识点。通过深入了解这些机制,读者可以对设备驱动程序的工作原理有清晰的认识。 此外,这本书还对Linux设备模型进行了详细的解释。它介绍了字符设备、块设备和网络设备等不同类型的设备,并讲解了它们在内核中的实现方式和工作原理。同,它还提供了许多实例和示例代码,方便读者理解和实践。 除了内核机制和设备模型,这本书还介绍了在Linux设备驱动程序开发过程中常用的工具和技术。比如,它详细介绍了调试技术、日志记录内核模块参数传递等实用的开发技巧。这些内容对于提高驱动程序的稳定性和可靠性非常有帮助。 总之,"深入Linux设备驱动程序内核机制"是一本非常重要的图书,它深入探讨了Linux设备驱动程序的内核机制,提供了丰富的实例和示例代码,帮助读者更好地理解和应用设备驱动程序开发技术。无论是对于初学者还是有经验的开发者来说,这本书都是不可或缺的学习资料。 ### 回答3: 《深入Linux设备驱动程序内核机制》是一本在CSDN上提供的PDF文档,涵盖了Linux设备驱动程序的内核机制。本书主要介绍了Linux内核中设备驱动的基本知识、原理和设计方法。其中详细讲解了设备驱动程序的注册、驱动与设备的交互、设备的初始化和释放、设备操作的原理等内容。 该书着重介绍了字符设备驱动、块设备驱动和网络设备驱动等常见类型的设备驱动程序的内核实现机制。对于想要了解Linux设备驱动开发的开发人员来说,这本书提供了非常有价值的知识,可以帮助他们理解和掌握设备驱动程序的编写和调试技巧。 该书从理论和实践的角度出发,结合了大量的源代码示例和实际案例,使读者更好地理解和掌握设备驱动程序的内核机制。此外,书中还介绍了设备和设备模型的相关知识,以及错误处理和调试技术。通过阅读和学习该书,读者可以更好地理解和应用Linux设备驱动程序的内核机制。 总之,《深入Linux设备驱动程序内核机制》这本PDF文档对于想要深入理解Linux设备驱动程序的人来说是非常有价值的资料。通过CSDN渠道获取该文档可以方便大家进行学习和参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值