CentOS下top命令引起系统负载升高
时间:2014-11-30 02:16 来源:linux.it.net.cn 作者:IT
二、分析过程
-
通过strace命令打印top进程信息,出现大量重复的系统调用,如下:
查看top进程的fd信息如下:
通过以上信息推断,top进程的输入输出异常,触发select返回文件描述符,但是文件描述符未处理或处理不当造成select无限触发,陷入死循环,占用一个核的cpu资源。
-
下载procps-3.2.8查看top源码,在第3398行找到了相关调用。如代码所示标准输入被放到了select调用里,因为标准输入错误导致触发select返回fd,无tv时长的等待。
-
long
file_flags;
int
rc;
char
c;
fd_set fs;
FD_ZERO(&fs);
FD_SET(STDIN_FILENO, &fs);
file_flags = fcntl(STDIN_FILENO, F_GETFL);
if
(file_flags==-1) file_flags=0;
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK|file_flags);
// check 1st, in case tv zeroed (by sig handler) before it got set
rc = chin(0, &c, 1);
if
(rc <= 0) {
// EOF is pretty much a "can't happen" except for a kernel bug.
// We should quickly die via SIGHUP, and thus not spin here.
// if (rc == 0) end_pgm(0); /* EOF from terminal */
fcntl(STDIN_FILENO, F_SETFL, file_flags);
select(1, &fs, NULL, NULL, &tv);
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK|file_flags);
}
if
(chin(0, &c, 1) > 0) {
fcntl(STDIN_FILENO, F_SETFL, file_flags);
do_key((unsigned)c);
}
else
{
fcntl(STDIN_FILENO, F_SETFL, file_flags);
}
-
标准输入的处理代码如下:
-
static
int
chin (
int
ech,
char
*buf, unsigned cnt)
{
int
rc;
fflush
(stdout);
if
(!ech)
rc = read(STDIN_FILENO, buf, cnt);
else
{
tcsetattr(STDIN_FILENO, TCSAFLUSH, &Savedtty);
rc = read(STDIN_FILENO, buf, cnt);
tcsetattr(STDIN_FILENO, TCSAFLUSH, &Rawtty);
}
// may be the beginning of a lengthy escape sequence
tcflush(STDIN_FILENO, TCIFLUSH);
return
rc;
// note: we do NOT produce a vaid 'string'
}
-
rc为read返回值,分三类返回正的字节数、0(表示到达文件末尾)、返回负值-1(表示出错)。源码中 rc==0的情况被注掉了(如下图)造成标准输入fd未处理,select的超时未生效。
-
top在运行时出现标准输入异常这种情况一般是很难发生的,通过ps -fe 打印出了top的进程关系如下:
sudo 进程的pid变成1了,通过 ssh -t ‘strace -o sudo.strace sudo -i’ 抓取信息如下:
在终端异常关闭(断网)后,并未退出,将父进程设成1继续运行。
-
经测试在ssh -t ‘sudo -i’ 这种方式登陆,sudo的父进程是sshd,当终端异常关闭时sudo会将自己的父进程改为1继续运行。正常登陆后在运行sudo的,sudo的父进程一般为bash(也可能是其他shell),不会出现类似情况。
三、总结
-
setsid top 或者其他守护进程调用top,当终端异常退出引起标准输入错误时都会造成top异常。
-
ubuntu系统不存在此问题,debian修复了此问题。
nicai@bugaosuni:~/procps-3.2.8/debian/patches$ cat top_stdin_eof.patch
Description: Check
for
stdin eof
if
term
Author: Samuel Thibault <samuel.thibault@ens-lyon.org>
Bug-Debian: http:
//bugs.debian.org/458986
Reviewed-by: Craig Small <csmall@debian.org>
Index: b/top.c
===================================================================
--- a/top.c 2009-11-24 21:00:34.000000000 +1100
+++ b/top.c 2009-11-24 21:00:35.000000000 +1100
@@ -3408,9 +3408,8 @@
// check 1st, in case tv zeroed (by sig handler) before it got set
rc = chin(0, &c, 1);
if
(rc <= 0) {
-
// EOF is pretty much a "can't happen" except for a kernel bug.
-
// We should quickly die via SIGHUP, and thus not spin here.
-
// if (rc == 0) end_pgm(0); /* EOF from terminal */
+
if
(rc == 0) end_pgm(0);
/* EOF from terminal, may happen if top
+ * erroneously gets detached from it. */
fcntl(STDIN_FILENO, F_SETFL, file_flags);
select(1, &fs, NULL, NULL, &tv);
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK|file_flags);
nicai@bugaosuni:~/procps-3.2.8/debian/patches$
(责任编辑:IT)
|
|
long file_flags; int rc; char c; fd_set fs; FD_ZERO(&fs); FD_SET(STDIN_FILENO, &fs); file_flags = fcntl(STDIN_FILENO, F_GETFL); if (file_flags==-1) file_flags=0; fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK|file_flags); // check 1st, in case tv zeroed (by sig handler) before it got set rc = chin(0, &c, 1); if (rc <= 0) { // EOF is pretty much a "can't happen" except for a kernel bug. // We should quickly die via SIGHUP, and thus not spin here. // if (rc == 0) end_pgm(0); /* EOF from terminal */ fcntl(STDIN_FILENO, F_SETFL, file_flags); select(1, &fs, NULL, NULL, &tv); fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK|file_flags); } if (chin(0, &c, 1) > 0) { fcntl(STDIN_FILENO, F_SETFL, file_flags); do_key((unsigned)c); } else { fcntl(STDIN_FILENO, F_SETFL, file_flags); } |
标准输入的处理代码如下:
|
static int chin ( int ech, char *buf, unsigned cnt) { int rc; fflush (stdout); if (!ech) rc = read(STDIN_FILENO, buf, cnt); else { tcsetattr(STDIN_FILENO, TCSAFLUSH, &Savedtty); rc = read(STDIN_FILENO, buf, cnt); tcsetattr(STDIN_FILENO, TCSAFLUSH, &Rawtty); } // may be the beginning of a lengthy escape sequence tcflush(STDIN_FILENO, TCIFLUSH); return rc; // note: we do NOT produce a vaid 'string' } |
rc为read返回值,分三类返回正的字节数、0(表示到达文件末尾)、返回负值-1(表示出错)。源码中 rc==0的情况被注掉了(如下图)造成标准输入fd未处理,select的超时未生效。
top在运行时出现标准输入异常这种情况一般是很难发生的,通过ps -fe 打印出了top的进程关系如下:
sudo 进程的pid变成1了,通过 ssh -t ‘strace -o sudo.strace sudo -i’ 抓取信息如下:
在终端异常关闭(断网)后,并未退出,将父进程设成1继续运行。
经测试在ssh -t ‘sudo -i’ 这种方式登陆,sudo的父进程是sshd,当终端异常关闭时sudo会将自己的父进程改为1继续运行。正常登陆后在运行sudo的,sudo的父进程一般为bash(也可能是其他shell),不会出现类似情况。
三、总结
-
setsid top 或者其他守护进程调用top,当终端异常退出引起标准输入错误时都会造成top异常。
-
ubuntu系统不存在此问题,debian修复了此问题。
|
nicai@bugaosuni:~/procps-3.2.8/debian/patches$ cat top_stdin_eof.patch Description: Check for stdin eof if term Author: Samuel Thibault <samuel.thibault@ens-lyon.org> Bug-Debian: http: //bugs.debian.org/458986 Reviewed-by: Craig Small <csmall@debian.org> Index: b/top.c =================================================================== --- a/top.c 2009-11-24 21:00:34.000000000 +1100 +++ b/top.c 2009-11-24 21:00:35.000000000 +1100 @@ -3408,9 +3408,8 @@ // check 1st, in case tv zeroed (by sig handler) before it got set rc = chin(0, &c, 1); if (rc <= 0) { - // EOF is pretty much a "can't happen" except for a kernel bug. - // We should quickly die via SIGHUP, and thus not spin here. - // if (rc == 0) end_pgm(0); /* EOF from terminal */ + if (rc == 0) end_pgm(0); /* EOF from terminal, may happen if top + * erroneously gets detached from it. */ fcntl(STDIN_FILENO, F_SETFL, file_flags); select(1, &fs, NULL, NULL, &tv); fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK|file_flags); nicai@bugaosuni:~/procps-3.2.8/debian/patches$ |