脚本在posix上限制进程

时间:2010-12-04 19:32:24

标签: c bash

我需要调用system()和popen()来运行命令cmd,这样进程是有时间限制的。如果进程完成,结果代码必须相同,否则必须能够检测到它超时。必须在posix系统上运行(至少在Linux和OSX上)。

至少在OSX上sh -c“ulimit -tn; cmd”从交互式提示符工作,(ulimit -tn; cmd)工作但是也限制了命令进程(使得shell无用) )。那可能没关系。

外部脚本是不受欢迎的:它使得使用它的程序很难移动,如果我愿意忍受它,我可以写一个C程序。

我的替代方法是在我的程序中使用fork()/ exec():我可以这样做,但代码相当丑陋(需要至少两个分叉,弄乱文件描述符),并且只运行可执行文件。 / p>

Linux有一个定时进程程序,但它在OSX上不可用(我找不到源代码)。在SO上提出了一个类似的问题,要求略有不同:我需要正确的返回码,并且必须使用popen()。

2 个答案:

答案 0 :(得分:1)

有一个Perl脚本here,它保留完成的命令的退出代码,如果超时则返回255。它与timeoutWietse Venema的作者)编写的Postfix的作用类似,并且首先作为SATAN的一部分发布。

Here is the C source到版本7(2008-10-05)版本的GNU coreutils的不同版本。

答案 1 :(得分:0)

GNU coreutils上有一个程序'超时',可以很容易地为MacOS X编译(我在我的机器上找到它,寻找我自己的同名程序)。

或者,我有一个我最初在1989年左右编写的版本。为了使它在MacOS X上正常工作,需要一些技巧。(如果你需要代码,请联系我 - 查看我的个人资料< / em>的。)


这是一个近似你想要的shell脚本。它无法满足您的所有要求:具体而言,它无法可靠地中继退出状态。它已经足够扭曲,使C程序变得微不足道。 shell(bash,无论如何)没有帮助不重新评估子shell的'$$'(当前进程ID);它坚持认为子shell的进程ID与父进程相同。

#!/bin/bash
# timeout -t time [-s signal] cmd [arg ...]

usage()
{
    echo "Usage: $(basename $0 .sh) -t time [-s signal] cmd [arg ...]" 1>&2
    exit 1
}

signal=15
while getopts t:s: opt "$@"
do
    case $opt in
    (t) time=$OPTARG;;
    (s) signal=$OPTARG;;
    (*)  usage;;
    esac
done
shift $((OPTIND - 1))
[ $# -ne 0 ]   || usage
[ -n "$time" ] || usage

pid_top=$$
# Run the command
(
    ("$@") &
    pid_sub=$!
    echo $pid_sub > .pid.$pid_top
    trap "kill $signal $pid_sub 2>/dev/null;
          kill 15 $pid_top 2>/dev/null; exit 1" 15
    wait
    kill $signal $pid_top 2>/dev/null
) &
pid_shell=$!
pid_cmd=$(cat .pid.$pid_top; rm -f .pid.$pid_top)
# Watchdog timer
(sleep $time; kill $signal $pid_cmd 2>/dev/null;
 kill -15 $pid_shell $pid_top) &
pid_watch=$!
# Cleanup!
trap "kill $signal $pid_cmd 2>/dev/null;
      kill 15 $pid_shell $pid_watch 2>/dev/null; exit 1" 15
wait

参数处理逻辑基本上是标准的 - 你必须指定一个时间,你可以指定一个信号,你必须指定一个命令,你可以为命令指定参数。

主shell脚本在变量pid_top中记录了自己的进程ID,以方便子shell(尽管'$$'可能会起作用)。

然后主shell运行一个后台子shell来运行命令,但这里有必要的恶作剧。首先,子shell在后台运行实际命令,捕获pid_sub中的子进程ID。然后它将该PID回传到文件'.pid。$ pid_top',以便父shell可以读取它并安排在看门狗超时时向实际命令发送死亡威胁。然后它安排捕获信号15(SIGTERM);在接收到这样的信号时,它用命令行上请求的信号杀死实际命令,并且还向父进程发送终止信号。然后它进入等待。如果命令完成,它会向父进程发送一个终止信号,让父进程知道一切正常。

回到主shell,它捕获pid_shell中后台子shell的进程ID以及pid_cmd中的实际命令。然后它运行另一个子shell,即看门狗进程。看门狗在超时期间安排休眠,当该时间结束时,向实际命令发送信号,并向父进程发送终止信号。

所以,现在有三个进程在后台运行(哎呀 - 这很令人困惑);实际命令,等待命令完成的子shell和看门狗定时器。

主shell捕获看门狗的PID并安排捕获信号15.收到信号后,它将相关的终止信号发送到实际命令,并向子shell和看门狗发送终止信号,即使其中至少有一个不存在。

主壳终于等待它的孩子死了。它从来没有真正从等待中恢复;一个信号唤醒它并且陷阱执行,但最终结果是它一直挂起,直到进程死亡。

然后退出......

要从执行的命令中获取准确的状态很难。麻烦的是,如果shell在后台运行进程,则无法获得其退出状态;但如果你同步运行它(所以你可以得到退出状态),那么看门狗和主进程不知道它的PID,并且不能确保它在计时器用完时终止。我可能只是错过了显而易见的事情 - 我当然希望如此,因为那剧本是可怕的!对我自己来说,我会坚持使用C程序;它只使用两个进程并将干净的实际命令状态恢复。