我知道,由于类 Unix 内核的 PID 重用,如果在 PID 已经被收获之后发送信号,则信号可能会被传送到错误的进程。
下文的讨论可能必然取决于我们讨论的具体内核,因此我很乐意将讨论范围缩小到 Linux。不过我欢迎其他内核专家的回答。
需要考虑的几种情况:
假设我正在调用
kill(2)
僵尸进程(即,我已经在内核空间中并执行内核代码来启动信号)。同时,僵尸进程的父进程调用wait(2)
。我对的调用是否kill(2)
可能最终尝试对其他进程采取行动?假设我已经对
kill(2)
一个进程执行了 -ed(即成功从调用返回到用户空间),但在传递我的信号之前,另一个信号被捕获并终止了该进程。在这种情况下,我假设我的信号肯定会被丢弃?原因之一是:即使 PID 被捕获并且同时生成了具有相同 PID 的另一个进程,将信号传递给新进程也可能会打开权限漏洞。
谢谢
是的。从进程发出
kill
系统调用到内核决定终止哪个进程之间可以有任意长的时间。其中一部分时间用于准备系统调用,一部分时间用于内核。在处理过程中的任何时刻,内核都可能决定切换到其他线程的执行(由于中断或因为内核试图获取已经获取的锁)。正在运行的进程kill
和正在运行的进程wait
可能由不同的处理器执行系统调用,因此它们甚至可能同时处于内核模式。系统
kill
调用没有太多事情要做,因此中断的可能性不大。但这种情况可能发生,而且如果你尝试足够多次,就会发生。而在多处理器系统上,竞争更有可能发生。您可以通过查看源代码来了解 Linux 内核如何处理系统调用。Elixir提供了一个带有交叉引用的便捷在线查看器。
kill
信号的实现以 开头SYSCALL_DEFINE2(kill, …)
(2 是因为有两个参数)。如您所见,代码由非常短的函数prepare_kill_siginfo
、然后是函数的开头kill_something_info
直到该函数调用组成read_lock(&tasklist_lock)
。一旦该调用返回,就不能向进程表添加或从中删除任何条目,特别是 的条目pid
一直指定同一个进程,直到函数返回之前的相应调用read_unlock
。这只留下很短的时间用于中断发生或其他处理器首先获取任务列表锁,但该时间不是也不可能是零。某些内核(例如 OpenBSD)会在进程对象被销毁后不久阻止重用 PID。这样一来,如果系统负载较轻(如果只有您在 PC 上除了尝试让两个进程竞争外什么都不做),则竞争条件不太可能发生,甚至根本不可能发生。但如果系统负载足够大(创建和终止大量进程),竞争条件仍然可能发生。没有办法阻止竞争。
由于存在竞争的可能性,多进程软件的设计通常应安排进程由其父进程或至少由其组长进行管理。父进程可以确保它永远不会试图终止它已在等待的进程。
是的,一旦您的进程从系统调用返回
kill
,内核就会决定信号针对哪个进程(或僵尸进程)。信号可能尚未处理,如果目标已经是僵尸进程,则信号将永远不会被传递。但内核会在执行系统调用期间选择信号针对哪个进程表条目。