1. 程式人生 > >容易忽略的expect指令碼問題,暗藏的殭屍程序,wait命令不要漏掉

容易忽略的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
  }

最後

程式碼不在乎簡單複雜,很多東西雖然可以省略,但能做到步步為營也不失是一種優秀的品質。