为什么有时候killall找不到进程?

前言

在Linux下有很多命令用于杀死进程,它们可以用于不同的场景,例如通过进程名杀死进程,通过pid杀死进程。这些方法我不准备一一列举,本文想说明的一个问题是,为什么明明通过ps找到了进程,但是通过killall却说找不到呢?你说你没有遇到过这样的问题?那你更要注意了!

killall简介

与kill不同的是,killall可以根据进程名来杀死进程,不像kill,可能先需要使用ps(可以参考《ps命令实例详解》)找到进程id,然后发送信号,就像下面这样:

1
2
3
$ ps -ef|grep hello
root 15530 6335 0 14:55 pts/4 00:00:00 ./hello
$ kill -9 15530

这样进程就被我们杀死了,我们可以直接使用killall:

1
$ killall hello

是不是觉得方便多了?

而且由于killall是根据名称杀死进程,因此如果当前运行着大量的hello程序,那么可以一次性杀死所有hello程序。

除此之外,它还有很多参数,例如忽略大小写,根据模式匹配进程名,杀死某个时间的进程等等,这里就不详解介绍了,有兴趣的可以查看man killall手册。

今天这里想要说明的是一种killall失效的情况。

killall失效了?

我写了一个自己的hello程序,然后尝试使用killall杀死正在运行的hello程序。

1
2
$ killall hello
hello: no process found

什么?竟然说找不到?一个ps丢过来:

1
2
$ ps -ef|grep hello
root 15765 6335 0 15:05 pts/4 00:00:00 ./hello

所以killall你到底行不行?

为何

为了找出killall失效的原因,我们必须知道它到底是如何通过进程名找到进程的。
这个时候就需要祭出我们的神器strace了,看看killall杀死一个普通进程到底做了哪些事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ strace killall hello
open("/proc/100/cmdline", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "", 1024) = 0
close(3) = 0
open("/proc/104/stat", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "104 (ipv6_addrconf) I 2 0 0 0 -1"..., 1024) = 161
close(3) = 0
open("/proc/113/stat", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "113 (kstrp) I 2 0 0 0 -1 6923888"..., 1024) = 153
close(3) = 0
open("/proc/137/stat", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "137 (charger_manager) I 2 0 0 0 "..., 1024) = 163
close(3) = 0
open("/proc/137/cmdline", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "", 1024) = 0

打印结果很多,我只提取了部分,可以看到的是,killall会去proc文件系统(proc文件系统可以参考《Linux中不可错过的信息宝库》)下查找各个进程id下的stat文件和cmdline,stat文件是怎样的呢?我们随便找一个看看:

1
2
$ cat /proc/16131/stat
16131 (hello) S 6335 16131 6335 34820 16131 1077936128 70 0 0 0 0 0 0 0 20 0 1 0 2465163 4321280 194 18446744073709551615 4194304 4196092 140734969955328 0 0 0 0 0 0 1 0 0 17 0 0 0 0 0 0 6295056 6295608 19099648 140734969962804 140734969962812 140734969962812 140734969966576 0

我们不要被这么多内容吓到了,可以明显看到的是,里面有hello啊。至此我们可以猜测,killall命令会去读取进程在proc文件系统中的stat文件里的名字。那么如果这么名字和你要杀死的进程对不上不就找不到了吗?

至此,想必你已经明白前面问题的原因了。

如何给自挖坑

那么怎样修改stat中显示的名字呢?我们可以使用prcl函数,话不多少,直接看示例代码:

1
2
3
4
5
6
7
8
9
10
11
//来源:公众号【编程珠玑】
//作者:守望先生
#include<stdio.h>
#include<sys/prctl.h>
#include<unistd.h>
int main(void)
{
prctl(PR_SET_NAME,"bianchengzhuji");
sleep(100);//防止进程立即退出,便于观察
return 0;
}

这个时候再编译运行程序查看stat和status中的名字:

1
2
3
4
5
6
7
8
9
10
11
$ gcc -o hello hello.c
$ cat /proc/pid/stat #这里的pid换成示例的进程id
16441 (bianchengzhuji) S 6335 16441 6335 34820 16441 1077936128 69 0 0 0 0 0 0 0 20 0 1 0 2535513 4321280 156 18446744073709551615 4194304 4196188 140724949606512 0 0 0 0 0 0 1 0 0 17 3 0 0 0 0 0 6295056 6295616 31719424 140724949614900 140724949614908 140724949614908 140724949618672 0
$ more /proc/pid/status
Name: bianchengzhuji
Umask: 0002
State: S (sleeping)
Tgid: 16441
Ngid: 0
Pid: 16441
PPid: 6335

是不是发现名字变了呢?虽然进程名还是hello,但是killall已经找不到它了,不过:

1
$ killall bianchengzhuji

还是可以的。

为什么会出现这种情况呢?
想象一下,你们公司内部不想重复造轮子,搞了一套开发框架,main函数在框架里写好了,通过库的形式给你们使用,可能名字早就定好了。

但是,这里需要特别注意的是,如果名字超过了15个字符,在stat和status文件中看到的将会看到被截断的名字

玩点刺激的

既然看到这里了,不如再玩点刺激的。

看看下面的代码:

1
2
3
4
5
6
7
8
9
10
//来源:公众号【编程珠玑】
#include<stdio.h>
#include<string.h>
#include<unistd.h>
int main(int argc,char *argv[])
{
strncpy(argv[0],"bianchengzhuji",sizeof("bianchengzhuji"));
sleep(100);
return 0;
}

是不是发现和前面例子的main函数不一样?参考这里(《C语言的main到底该怎么写》)
这个时候你去编译运行:

1
2
$ gcc -o hello hello.c
$ ./hello

然后尝试使用ps去查找进程:

1
2
$ ps -ef|grep hello
root 17831 17818 0 16:09 pts/26 00:00:00 grep --color=auto hello

然后你就会惊喜的发现找不到hello进程。
但是使用:

1
2
3
4
$ ps -ef|grep shouwangxiansheng
root 17938 6335 0 16:12 pts/4 00:00:00 shouwangxiansheng
root 17954 17924 0 16:12 pts/27 00:00:00 grep --color=auto shouwangxiansheng
hyb@ub

就可以找到。

这种情况下直接改变了程序的命令名,因此ps之类的找不到。
这个时候看命令名是什么呢?

1
2
$ cat /proc/17938/cmdline
shouwangxiansheng

不过这个时候killall还是可以找到它!也就是你可以使用killall hello杀死它。

总结

如果你发现你的程序无法通过killall 进程名的方式杀死的话,不妨看看proc文件系统中这个进程的stat文件或者status文件中的名。

守望 wechat
关注公众号[编程珠玑]获取更多原创技术文章
出入相友,守望相助!