容易忽略的expect脚本问题,暗藏的僵尸进程,wait命令不要漏掉

问题描述

前几天有个小需求,用到expect脚本去循环的发送一些数据,主要问题代码如下:

 #! /usr/bin/expect
 while {true} {
  set timeout 60
  spawn telnet ip port
  expect "]'.";
  send "***********一些数据***********\r"
  expect "*********一些回复************";
  send "exit\r"
  expect "Connection closeed by foreign host.";
  expect eof
  sleep 10
  }

单单看到这段代码,并没有发现什么问题,但是运行几个小时之后,收到一个错误:

 buffer overflow detected *** : /usr/bin/expect terminated 
 ========Backtrace: ========
 ... ...

内存越界?开始排查原因,虽然走了一些弯路,不过终于还是找到了答案:
首先,我们查看该进程的pid,然后进一步通过pid去进程fd文件夹下看看

[root]# ps -ef|grep expect.sh
root  17761 30111  .....
[root]# cd /proc/17761/fd/
[root]# ls -a
...

可以看到该进程已经产生了非常多的子进程句柄,赶紧ps -ef|grep 17761看一下:

[root]# ps -ef|grep 17761
root 23424 17761 ... [telnet] <defunct>
root 23426 17761 ... [telnet] <defunct>
root 23431 17761 ... [telnet] <defunct>
root 23434 17761 ... [telnet] <defunct>
root 23438 17761 ... [telnet] <defunct>
root 23439 17761 ... [telnet] <defunct>
root 23455 17761 ... [telnet] <defunct>
...

原来这个脚本产生了大量的telnet僵尸进程,导致句柄都用完了,但看上面的脚本,每次telnet都退出了,也都expect eof结束了进程,怎么会变成僵尸进程呢?

原因分析

很多人expect脚本用的不多,基本都是参照网上的例子来完成自己的需求,然后网上的博客大多都是转载来转载去,这导致有些细节问题不常被提及。
首先spawn会开启一个子进程(spawn_id)去执行命令,expect eof用于等待进程结束,另外有一个close命令用于直接结束子进程(根据spawn_id来定位子进程)。问题在于eof 和 close都只是杀死子进程,但子进程变为僵尸进程依然存在进程列表中,僵尸进程会占用句柄,但是句柄是有限的,大量僵死进程的产生,导致整个脚本进程无法继续运行,报错退出。
网上的例子大多是流程式的脚本,不是本文这种循环运行的,所以等待eof后,主进程退出,将僵尸进程也回收了,因此不会有任何问题。
不退出主进程,还要及时回收僵尸子进程,很多语言都内置了相关的方法,expect脚本也不例外,wait就是负责给子进程收尸的。所以文章开头的脚本应该加上wait来及时回收telnet僵尸进程。

 #! /usr/bin/expect
 while {true} {
  set timeout 60
  spawn telnet ip port
  expect "]'.";
  send "***********一些数据***********\r"
  expect "*********一些回复************";
  send "exit\r"
  expect "Connection closeed by foreign host.";
  expect eof  <---------此句可以换成 close,因为上面已经主动关闭了telnet
  wait   <----------------wait不可缺少
  sleep 10
  }

最后

代码不在乎简单复杂,很多东西虽然可以省略,但能做到步步为营也不失是一种优秀的品质。

posted @ 2018-12-14 11:39  御街打码  阅读(1376)  评论(0编辑  收藏  举报