Contents

检查杀死Linux进程的原因

1. 概述

在使用 Linux 系统时,我们最终将需要**弄清楚是什么杀死了我们的进程以及原因。**在本文中,我们将解释如何解决该问题。

我们将首先了解一个进程在 Linux 中是如何终止的。接下来,我们将展示在哪里可以找到内核决定终止进程时的相关日志。最后,我们将研究启动该程序的原因。

2. 了解进程如何终止

在 Linux 中,进程终止通常有两种方式:

  • 自愿:调用**exit()系统调用。这意味着进程已经完成了它的任务,所以它选择终止。
  • 不由自主地:接收到信号时。该信号可以由另一个用户、另一个进程或 Linux 本身发送。

我们将在这里关注后一种情况,看看信号是什么以及它们是如何工作的。然后,我们将讨论与进程终止相关的那些信号。

2.1. Linux 信号简介

信号 是在 Linux 中进行进程间通信 (IPC) 的方式之一。**当一个进程接收到一个信号时,它会停止其正常的执行路径,**除非它明确忽略该特定信号,否则它会继续执行相应的信号处理程序。

这个信号处理程序是一个小例程,它指示进程在接收到特定信号时应该做什么。一个进程可以选择为其中一个或多个定义其信号处理程序,也可以选择使用 Linux 提供的默认处理程序。正如我们将在后面的部分中看到的,有一个信号是我们不能忽略或覆盖的——SIGKILL 信号。

2.2. 终止信号

现在,让我们简单提一下与终止进程相关的那些信号

  • SIGTERM:这是一种要求进程终止的“好”方式,这意味着它可以执行一些清理操作并优雅地关闭。
  • SIGINT:表示用户通过发送 INTR 字符 (CTRL + c) 中断了进程。
  • SIGQUIT:与 SIGTERM 类似,只是它在终止过程中也会产生一个核心 tump。
  • SIGHUP:这表明用户的终端由于某种原因断开连接。
  • SIGKILL:这个特殊信号不能被忽略或处理,它会立即杀死进程。

在下一节中,我们将快速提到 SIGKILL,因为这是 Linux 在需要时终止我们的进程的方式。

2.3. SIGKILL 信号

当一个进程收到 SIGKILL 时,它听不到子弹来了。与 SIGTERM 或 SIGQUIT 不同,我们不能以不同的方式阻止或处理 SIGKILL。因此,当我们需要立即终止进程时,它通常被视为最后的手段。

尝试终止进程时的一种常见做法是首先尝试使用 SIGTERM 或 SIGQUIT,如果在合理的时间后它没有停止,则通过 SIGKILL 强制它。

正如我们将在后面的部分中看到的那样,Linux 也可能向进程发送 SIGKILL 以在操作系统遇到资源利用问题时强制立即终止。

3. 找出谁杀死了进程

现在我们已经了解了进程是如何被杀死的,我们可以查看进程终止的根本原因并找到有关它的更多信息。

假设进程不是自愿退出的,那么终止进程的唯一方法就是通过我们前面提到的那些信号。这些信号可以由另一个进程或 Linux 内核发送。我们将在接下来的部分中检查这两种情况,但更多地关注内核启动的进程终止。

3.2. 处理启动的信号

在许多情况下,某些其他用户或进程可能会选择终止某个进程。如前所述,这最终将通过 SIGTERM 或 SIGKILL 信号(或两者的组合)发生。

这是一个使用pkill命令 停止进程的用户示例。

我们首先打开一个终端并创建一个休眠 20 秒的新进程:

$ sleep 20

然后,从另一个终端:

$ pkill sleep

最后,回到我们的第一个终端,我们得到:

$ sleep 20
Terminated

请注意,有这条Terminated消息,表示进程已终止。但是,如果该进程在终端中未处于活动状态(例如,因为用户从图形用户界面或cron作业启动它),则尝试了解发生的情况将更加困难。因此,我们必须提前进行设置以监控此类用户活动。*pssact auditd *等工具可以帮助我们实现这一目标。

3.3. 内核启动信号

当系统资源不足时,Linux 内核也可能决定终止一个或多个进程。一个非常常见的例子是内存不足 (OOM) ,它在系统的物理内存耗尽时采取行动。

当此事件发生时,内核会将相关信息记录 到内核日志缓冲区中,该缓冲区可通过*/dev/kmsg 获得。有几个工具可以更轻松地从该虚拟设备读取数据,其中最流行的是dmesg *和journalctl 。让我们看一些例子。

首先,让我们触发OOM:

$ (echo "li = []" ; echo "for r in range(9999999999999999): li.append(str(r))") | python
Killed

现在,让我们使用dmesg检查相关日志:

$ sudo dmesg | tail -7
[427918.962500] [ 142394] 1000 142394 58121 2138 335872 0 0 Socket Process
[427918.962505] [ 179856] 1000 179856 660680 21527 1208320 0 0 Web Content
[427918.962508] [ 179902] 1000 179902 605502 3489 483328 0 0 Web Content
[427918.962510] [ 179944] 1000 179944 3197660 3175506 25534464 0 0 python
[427918.962514] oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,
global_oom,task_memcg=/user.slice/user-1000.slice/session-2.scope,task=python,pid=179944,uid=1000
<strong>[427918.962531] Out of memory: Killed process 179944 (python) total-vm:12790640kB, anon-rss:12702024kB,</strong>
file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:24936kB oom_score_adj:0
[427919.411464] oom_reaper: reaped process 179944 (python), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

我们还可以使用journalctl调查日志:

$ journalctl --list-boots | \
    awk '{ print $1 }' | \
    xargs -I{} journalctl --utc --no-pager -b {} -kqg 'killed process' -o verbose --output-fields=MESSAGE
Fri 2021-12-10 17:49:55.782801 UTC [s=38b35d6842d24a09ab14c8735cd79ff7;i=aeef;b=cd40ed63d47d4814b4c2c0f9ab73341f;m=63a20e170d;t=5d2ce59d50891;x=cdd950f6be42011b]
<strong>    MESSAGE=Out of memory: Killed process 179944 (python) total-vm:12790640kB, anon-rss:12702024kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:24936kB oom_score_adj:0</strong>

4. 内存不足

在本节中,我们将简要介绍 OOM 及其基本机制。

Linux有这个虚拟内存的概念,也就是说,从每个进程来看,整台计算机的全部物理内存都可以使用。这使得编程变得非常容易,因为它可以过度使用内存。这反过来又允许每个进程快速获得更多内存空间,即使另一个进程可能已经占用了可用内存。但是,如果所有进程都尝试同时使用相同的内存空间,那么就会出现完整性问题。这就是OOM出现的地方。

OOM 的工作是在系统内存不足时选择最少的进程数并终止它们。它使用一个 badness 分数——可以通过procfs通过 /proc/pid/oom_score 获得——来决定要杀死哪些进程。在做出该决定时,它会尝试通过确保它:

  • 最大程度地减少丢失的工作
  • 尽可能多地恢复内存
  • 不会杀死无辜的进程,而只会杀死那些消耗大量内存的进程
  • 最小化被杀死进程的数量(理想情况下,只有一个)
  • 杀死用户期望的进程