2

为了验证我想使用的第三方二进制分布式软件的行为,我正在实现一个内核模块,其目标是跟踪该软件产生和终止的每个子节点。

目标二进制文件是 Golang 生成的二进制文件,它是多线程的。我编写的内核模块在内核函数_do_fork()do_exit()上安装了钩子,以跟踪该二进制文件产生和终止的每个进程/线程。

LKM 或多或少有效。

然而,在某些情况下,我有一个我无法解释的场景。似乎一个进程/线程可以在不通过do_exit()的情况下终止。

我通过printk()收集的证据显示了进程的创建,但并不表示进程的终止。

我知道printk()可能会很慢,而且我也知道在这种情况下消息可能会丢失。

为了防止由于控制台速度慢而导致消息丢失(对于这个特定的应用程序,使用串行 tty 115200),我尝试实现一个更快的控制台,并且使用netconsole收集了消息。

所描述的设置似乎证实了一个进程可以在不通过do_exit()函数的情况下终止。

但是因为我不确定我的消息不会在printk()基础架构上丢失,所以我决定重复相同的测试,但将printk()替换为ftrace_printk(),这应该是printk()的更精简的替代方案。

还是一样的结果,偶尔我看到进程没有通过do_exit(),并且验证PID当前是否正在运行,我不得不面对它没有运行的事实。

另请注意,我将钩子放在do_exit()内核函数中作为第一条指令,以确保函数流不会在被调用函数内终止。

我的问题如下:

Linux 进程可以在其流程不通过do_exit()函数的情况下终止吗?

如果是这样,有人可以告诉我这种情况可能是什么吗?

4

2 回答 2

4

经过长时间的调试会话,我终于能够回答我自己的问题。

那不是全部; 我还能够解释为什么我看到了我在场景中描述的奇怪行为。

让我们从头开始:监控一个高度多线程的应用程序。我观察到了罕见的情况,即突然停止的 PID 存在而没有观察其通过 Linux 内核do_exit()函数的流。

因为这是我原来的问题:

Linux 进程可以在不通过 do_exit() 函数的情况下终止吗?

至于我目前认为相当广泛的知识,Linux 进程在不通过do_exit()函数的情况下无法结束其执行。

但是这个答案与我的观察结果相反,导致我提出这个问题的问题仍然存在。

这里有人建议我看到的奇怪行为是因为我的观察有些错误,暗指我的方法不准确,至于我的结论。

我的观察是正确的,我观察的过程没有通过do_exit()而是终止了。

为了解释这种现象,我想提出另一个我认为互联网搜索者可能会觉得有用的问题:

两个进程可以共享同一个 PID 吗?

如果你在一个月前问我这个问题,我肯定会这样回答这个问题:“绝对不,两个进程不能共享同一个 PID。” 不过,Linux 更复杂。

有一种情况,在 Linux 系统中,两个不同的进程可以共享同一个 PID!

https://elixir.bootlin.com/linux/v4.19.20/source/fs/exec.c#L1141

令人惊讶的是,这种行为并没有伤害任何人;发生这种情况时,这两个进程之一就是僵尸进程。

如果线程领导者在其子线程之前终止,则预期的行为是将孩子的 PID 替换为领导者的 PID。这也意味着孩子的PID突然停止存在。

那就是我所看到的;我正在监视的 PID 没有通过do_exit(),因为当相应的线程终止时,它不再有它启动时的 PID,但它有它的领导者。

对于非常了解 Linux 内核机制的人来说,这不足为奇;这种行为是有意的,自 2.6.17 以来没有改变。现在的5.10.3,还是这样的。

希望这对互联网搜索者有用;我还想补充一点,这也回答了以下问题:

  • 问题:Linux 进程/线程可以在不通过 do_exit() 的情况下终止吗?答案:不,do_exit() 是进程必须结束其执行的唯一方法——有意而不是无意。
  • 问题:两个进程可以共享同一个 PID 吗?:一般不会。在极少数情况下,两个可调度实体具有相同的 PID。
  • 问题:Linux内核是否存在进程更改其PID的情况?:是的,至少有一种情况是进程更改其 PID。
于 2020-12-27T15:18:51.167 回答
-1

Linux 进程可以在其流程不通过 do_exit() 函数的情况下终止吗?

可能不是,但您应该研究一下Linux 内核的源代码以确保。在KernelNewbies上提问。内核线程和udevsystemd相关的东西(或者可能modprobe或更旧的hotplug)可能是例外。当您/sbin/init的 pid 1 终止(不应该发生)时,会发生奇怪的事情。

LKM 或多或少有效。

那是什么意思?内核模块怎么可能工作一半?


在现实生活中,有时你的 Linux 内核会出现恐慌或崩溃(如果你的LKM 没有经过 Linux 内核社区的同行评审,它可能会发生)。在这种情况下,不再有任何进程的概念,因为它们是由活的 Linux 内核提供的抽象。

另请参见dmesg(1)strace(1)proc(5)syscalls(2)ptrace(2)clone(2)fork(2)execve(2)waitpid(2)elf(5 ) ,凭证(7) , pthreads(7)

还要查看你的 libc 的源代码,例如GNU libcmusl-libc

当然,请参阅Linux From ScratchAdvanced Linux Programming

并验证 PID 当前是否正在运行,

这可以通过用户登陆/proc/,或使用带有 0 信号的kill(2)来完成(也可能是 pidfd_send_signal(2) ...)

PS。我还是不明白为什么你需要写一个内核模块或者改变内核代码。我的直觉是尽可能避免这样做。

于 2020-12-16T12:32:15.510 回答