在Linux上,如何理解kill -9对一个进程没有任何作用?

65

我正在编写一个插件,可以在访问网站时自动突出显示文本字符串。就像突出显示搜索结果一样,但是它可以用于很多单词;例如,当过敏者浏览食品网站时,可以使单词真正突出。

但是我遇到了问题。当我尝试关闭一个空的、新的FF窗口时,它会阻止整个进程。当我杀死该进程时,所有窗口都消失了,但Firefox进程仍然存在(父PID为1,不接受任何信号,有许多资源打开,仍在占用CPU,但不会动)。

因此有两个问题:

  1. 进程怎么可能不响应kill -9(无论是作为用户还是作为root)?

  2. 除了重启之外,我还能做些什么吗?

[编辑] 这是有问题的进程:

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
digulla  16688  4.3  4.2 784476 345464 pts/14  D    Mar28  75:02 /opt/firefox-3.0/firefox-bin

ps -ef | grep firefox相同。

UID        PID  PPID  C STIME TTY          TIME CMD
digulla  16688     1  4 Mar28 pts/14   01:15:02 /opt/firefox-3.0/firefox-bin

这是唯一剩下的进程。正如您所看到的,它不是僵尸进程,它正在运行!无论我是按PID还是进程名称杀掉它,它都不会响应kill -9命令!如果我试图使用 strace 连接它,那么strace也会挂起并且无法被终止。也没有任何输出。我猜测火狐卡在了某个内核例程中,但是是哪一个呢?

[EDIT2]根据sigjuice的反馈:

ps axopid,comm,wchan

可以向您展示进程挂起的内核例程。在我的情况下,有问题的插件是Beagle索引器(openSUSE 11.1)。禁用该插件后,Firefox又快又乐。


3
我认为“与编程无关”这个标签太严格了。如果Aaron自己修改了Firefox代码,并且问了一个关于Linux的问题,那么这就是与编程有关的。毫无疑问,操作系统内核行为与编程有某些关系。 - Steve Jessop
2
@Aaron,STAT列显示“D”,表示“不可中断的睡眠”。处于此状态的进程根本无法被终止。您的主目录是否已挂载NFS,或者Firefox以其他方式访问了NFS目录? - sigjuice
2
@Aaron "ps axopid,comm,wchan" 可能会向您显示 Firefox 被卡在哪个内核例程中。 - sigjuice
2
这与编程有关,例如在构建系统或自动化测试框架中,它可能非常适合于shell脚本编写。 - Harvey
2
这个问题在U&L上有一个重复的。它们可以合并吗? - alexei
显示剩余6条评论
7个回答

126

正如对OP的评论所述,进程状态(STAT)为D表示该进程处于“不可中断休眠”状态。通俗地说,这通常意味着它正在等待I/O操作,不能/不会执行任何操作 - 包括死亡 - 直到该I/O操作完成。

处于D状态的进程通常只会在操作完成后的一小部分时间内停留,然后返回R/S状态。根据我的经验,如果一个进程被卡在D状态中,那么它很可能是在尝试与无法访问的NFS或其他远程文件系统进行通信,尝试访问一个失败的硬盘,或通过不稳定的设备驱动程序使用某个硬件设备。在这种情况下,恢复并允许该进程死亡的唯一方法是将fs/drive/hardware恢复运行,以便I/O操作可以完成,或者放弃并重新启动系统。对于NFS的特定情况,挂载也可能最终超时并从I/O操作中返回(带有失败代码),但这取决于挂载选项,并且将NFS挂载设置为永久等待非常常见。

这与僵尸进程的状态Z是不同的。


7
是的,可怕的磁盘休眠 :) +1,这是一个表述清晰的答案。 - Tim Post
2
哎呀,我猜想在陷入这种情况后有没有办法为NFS/SMB等挂载设置超时时间? - SamB
由于内核 bug 的原因,您可能会看到某些东西卡在 D 中。 - poolie
8
观察系统状况的好方法是运行“ps -o pid,wchan 1234”(将相关pid插入其中),这将告诉您它在内核中卡在哪个等待通道上。您可以将其用于错误报告,或通过Google搜索了解信息,这可能会给您提供一些线索——无论是卡在NFS中还是其他驱动程序中。 - poolie
好的回答!!!一个僵尸进程无法被杀死(实际上,它就是一个僵尸),你必须杀死父进程,这样僵尸进程就会有一个新的父进程(init),它总是在执行wait(2),最终在父进程的wait(2)上死亡。 - Luis Colorado

8

请仔细检查父进程ID是否为1。如果不是,并且这是firefox,首先尝试使用sudo killall -9 firefox-bin。之后,尝试使用sudo killall -9 [process-id]逐个杀死特定的进程ID。

一个进程如何可能无法响应kill -9(无论是用户还是root)?

如果一个进程变成了<defunct>,然后成为父进程为1的僵尸进程,则无法手动杀死它,只有init可以。僵尸进程已经死亡和消失了-它们已经失去了被杀死的能力,因为它们不再是进程,而只是一个进程表条目及其关联的退出代码,等待被收集。您需要杀死父进程,但由于明显的原因,您无法杀死init

但是,在此处查看更多一般信息。重新启动将自然地杀死所有进程。


1
实际上,僵尸进程并不是一个进程,而是一个进程表条目,其存在的唯一目的是向父进程发送SIGCHLD信号。Init会自动加入,因此您无法真正拥有PPID == 1的僵尸进程。 - vartec
我的理解是,kill -9 命令会在不问任何问题的情况下从进程表中删除进程。但似乎某些进程可以以某种方式防止这种情况发生。如何做到的? - Aaron Digulla
@vartec:你说得对,我表达得不太好。如果你想编辑它,请随便。@Aaron:已经死亡的进程不会监听kill信号(没有东西可以杀死,它们仍然需要被回收,所以它们还不能消失)。 - John Feminella
编辑以澄清,僵尸进程不再是实际进程。 - Dave Sherohman

1
我最近陷入了双分叉的陷阱,并在最终找到答案之前来到了这个页面。即使问题不同,症状也是相同的:

  • WYKINWYT:你杀死的不是你想象中的东西

下面是基于SNMP守护程序示例的最小测试代码:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

int main(int argc, char* argv[])
{
    //We omit the -f option (do not Fork) to reproduce the problem
    char * options[]={"/usr/local/sbin/snmpd",/*"-f","*/-d","--master=agentx", "-Dagentx","--agentXSocket=tcp:localhost:1706",  "udp:10161", (char*) NULL};

    pid_t pid = fork();
    if ( 0 > pid ) return -1;

    switch(pid)
    {
        case 0: 
        {   //Child launches SNMP daemon
            execv(options[0],options);
            exit(-2);
            break;
        }
        default: 
        {
            sleep(10); //Simulate "long" activity

            kill(pid,SIGTERM);//kill what should be child, 
                              //i.e the SNMP daemon I assume
            printf("Signal sent to %d\n",pid);

            sleep(10); //Simulate "long" operation before closing
            waitpid(pid);
            printf("SNMP should be now down\n");

            getchar();//Blocking (for observation only)
            break;
        }
    }
    printf("Bye!\n");
}

在第一阶段,主进程(7699)启动了SNMP守护进程(7700),但我们可以看到这个进程现在是Defunct/Zombie。此外,我们还可以看到另一个带有我们指定选项的进程(7702)。
[nils@localhost ~]$ ps -ef | tail
root       7439      2  0 23:00 ?        00:00:00 [kworker/1:0]
root       7494      2  0 23:03 ?        00:00:00 [kworker/0:1]
root       7544      2  0 23:08 ?        00:00:00 [kworker/0:2]
root       7605      2  0 23:10 ?        00:00:00 [kworker/1:2]
root       7698    729  0 23:11 ?        00:00:00 sleep 60
nils       7699   2832  0 23:11 pts/0    00:00:00 ./main
nils       7700   7699  0 23:11 pts/0    00:00:00 [snmpd] <defunct>
nils       7702      1  0 23:11 ?        00:00:00 /usr/local/sbin/snmpd -Lo -d --master=agentx -Dagentx --agentXSocket=tcp:localhost:1706 udp:10161
nils       7727   3706  0 23:11 pts/1    00:00:00 ps -ef
nils       7728   3706  0 23:11 pts/1    00:00:00 tail

经过10秒的模拟,我们将尝试终止唯一已知的进程(7700)。最终我们通过waitpid()成功了。但是进程7702仍然存在。
[nils@localhost ~]$ ps -ef | tail
root       7431      2  0 23:00 ?        00:00:00 [kworker/u256:1]
root       7439      2  0 23:00 ?        00:00:00 [kworker/1:0]
root       7494      2  0 23:03 ?        00:00:00 [kworker/0:1]
root       7544      2  0 23:08 ?        00:00:00 [kworker/0:2]
root       7605      2  0 23:10 ?        00:00:00 [kworker/1:2]
root       7698    729  0 23:11 ?        00:00:00 sleep 60
nils       7699   2832  0 23:11 pts/0    00:00:00 ./main
nils       7702      1  0 23:11 ?        00:00:00 /usr/local/sbin/snmpd -Lo -d --master=agentx -Dagentx --agentXSocket=tcp:localhost:1706 udp:10161
nils       7751   3706  0 23:12 pts/1    00:00:00 ps -ef
nils       7752   3706  0 23:12 pts/1    00:00:00 tail

在将一个字符传递给getchar()函数后,我们的主进程终止,但具有7002进程ID的SNMP守护进程仍然存在。

[nils@localhost ~]$ ps -ef | tail
postfix    7399   1511  0 22:58 ?        00:00:00 pickup -l -t unix -u
root       7431      2  0 23:00 ?        00:00:00 [kworker/u256:1]
root       7439      2  0 23:00 ?        00:00:00 [kworker/1:0]
root       7494      2  0 23:03 ?        00:00:00 [kworker/0:1]
root       7544      2  0 23:08 ?        00:00:00 [kworker/0:2]
root       7605      2  0 23:10 ?        00:00:00 [kworker/1:2]
root       7698    729  0 23:11 ?        00:00:00 sleep 60
nils       7702      1  0 23:11 ?        00:00:00 /usr/local/sbin/snmpd -Lo -d --master=agentx -Dagentx --agentXSocket=tcp:localhost:1706 udp:10161
nils       7765   3706  0 23:12 pts/1    00:00:00 ps -ef
nils       7766   3706  0 23:12 pts/1    00:00:00 tail

结论

我们忽略了双重分支机制,导致我们认为kill操作没有成功。但实际上,我们只是杀错了进程!!

通过添加-f选项(不进行(双重)分支),一切都按预期进行。


1

这个进程是否可能在你杀死它的同时被重新启动(例如通过init)?

你可以轻松地检查这一点。如果在kill -9 PID之后PID仍然相同,则该进程未被杀死,但如果PID已更改,则该进程已被重新启动。


0
sudo killall -9 firefox

应该有效 编辑:[PID] 更改为firefox

如果问题与多个实例有关,那么应该关闭它们所有,而kill -9 PID只会杀死指定的实例。 - karim79
有趣。当我写下我的评论时,答案是错误的。现在答案是正确的,而我的评论已经过时了。然而,答案上没有编辑历史记录。 - Jörg W Mittag
如果这能让你开心,Jorg,我会输入一个编辑。我们今天过得不顺吗? - karim79
1
这更多是对网站行为的观察。帖子可以被编辑这一事实是使SO运作的原因。然而,我不知道可以在不显示已编辑的情况下编辑帖子,这有点奇怪。 - Jörg W Mittag
我认为“X分钟前编辑”会在一分钟左右出现,这就是为什么编辑过的答案最初不被标记为已编辑。我也注意到了这一点。 - karim79

0

ps -ef | grep firefox; 你会看到三个进程,全部杀掉它们。


0

你也可以执行 pstree 并杀死父进程。这可以确保你获取整个有问题的进程树,而不仅仅是叶子节点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接