为了验证我想使用的第三方二进制分布式软件的行为,我实现了一个内核模块,其目标是跟踪该软件产生和终止的每个子程序。
目标二进制文件是Golang生成的,它是多线程的,我编写的内核模块在内核函数**_do_fork()和do_exit()上安装了钩子,以跟踪该二进制文件生成和终止的每个进程/线程。
LKM或多或少起作用了。
然而,在某些情况下,我有一个我无法解释的场景。看起来一个进程/线程可能会在没有通过do_exit()的情况下终止。
我通过输入printk()收集到的证据显示了进程创建,但没有指示进程终止。
我知道printk()可能会很慢,而且我也知道在这种情况下消息可能会丢失。
为了防止由于控制台太慢而导致消息丢失(对于这个特定的应用程序,使用了serial tty 115200),我尝试实现一个更快的控制台,并且使用netconsole收集了消息。
所描述的设置似乎确认了进程可以终止,而无需通过do_exit()函数。
但是因为我不确定我的消息是否会在printk()基础设施上丢失,所以我决定重复相同的测试,但用ftrace_printk()替换printk(),这应该是比printk()更精简的替代方案。
仍然是同样的结果,偶尔我会看到进程没有通过do_exit(),并验证PID当前是否正在运行,我不得不面对它没有运行的事实。
还要注意,我将钩子放在do_exit()**内核函数中作为第一条指令,以确保函数流不会在被调用函数内部终止。
我的问题如下:
- Linux进程是否可以在其流不经过**do_exit()*函数的情况下终止?
- 如果是这样,谁能给予我一个暗示,这个场景可能是什么?*
2条答案
按热度按时间niknxzdl1#
经过长时间的调试,我终于能够回答自己的问题了。
这还不是全部我还能够解释为什么我看到了我在场景中描述的奇怪行为。
让我们从头说起:我观察到了一些罕见的情况,即PID突然停止,而没有观察到它的流通过Linux内核**do_exit()**函数。
因为这是我最初的问题:
至于我目前的知识,我现在认为这是相当广泛的,一个Linux进程不能结束其执行没有通过**do_exit()**函数。
但这个答案与我的观察相反,导致我提出这个问题的问题仍然存在。
这里有人提出,我看到的奇怪行为是因为我的观察在某种程度上是错误的,暗指我的方法是不准确的,至于我的结论。
为了解释这一现象,我想提出另一个问题,我认为互联网搜索者可能会发现这个问题有点用处:
如果你一个月前问我这个问题,我肯定会回答:"绝对不行,两个进程不能共享同一个PID。"不过Linux要复杂得多。
在Linux系统中,有一种情况是两个不同的进程可以共享同一个PID!
https://elixir.bootlin.com/linux/v4.19.20/source/fs/exec.c#L1141
令人惊讶的是,这种行为并没有伤害任何人;当这种情况发生时,这两个进程之一是僵尸进程。
这个重复PID的情况比前面描述的要复杂得多。如果线程进程在调用execve之前分叉(分叉还复制线程),则进程必须刷新以前的exec上下文。如果打算使用execve()函数执行新文本,则内核必须首先调用
flush_old_exec()
函数,然后该函数为进程中除任务领导者之外的每个线程调用de_thread()函数。结果,除任务领导者之外,所有进程的线程都被消除。每个线程的PID都被更改为领导者的PID,如果它没有立即终止,例如,因为它需要等待操作完成,它将继续使用该PID。这就是我所看到的我正在监视的PID没有通过do_exit(),因为当相应的线程终止时,它不再具有它启动时的PID,但是它具有它的领导者的PID。
对于非常了解Linux内核机制的人来说,这并不奇怪;此行为是预期行为,并且自2.6.17以来未发生更改。当前的5.10.3仍然是这种方式。
希望这对互联网搜索者有用;我还想补充一点,这也回答了以下问题:
4nkexdtk2#
Linux进程是否可以在其流不经过do_exit()函数的情况下终止?
也许不是,但是你应该研究Linux kernel的源代码来确定。询问KernelNewbies。内核线程和udev或systemd相关的东西(或者
modprobe
或旧的hotplug
)可能是例外。当你的pid为1的/sbin/init
终止时(不应该发生),奇怪的事情会发生。LKM或多或少起作用了。
这是什么意思?内核模块怎么可能只工作一半?
在现实生活中,有时确实会发生Linux内核是panicking或崩溃的情况(如果没有经过Linux内核社区的同行评审,* 您的 * LKM也可能发生这种情况)。在这种情况下,不再有processes的概念,因为它们是由一个活的Linux内核提供的抽象。
另请参见dmesg(1)、strace(1)、proc(5)、syscalls(2)、ptrace(2)、clone(2)、fork(2)、execve(2)、waitpid(2)、elf(5)、credentials(7)和pthreads(7)
另请查看libc的源代码,例如GNU libc或musl-libc
当然,请参见Linux From Scratch和Advanced Linux Programming
以及验证PID当前是否正在运行,
这可以通过
/proc/
实现,也可以通过kill(2)和0信号实现(也可以是pidfd_send_signal(2) ...)附言:我还是不明白你为什么要写一个内核模块或者修改内核代码。我的直觉是尽可能避免这样做。