ソフトウェアエンジニア現役続行

雑多なことを綴ります

タイムアウトを設定してsystem関数を使いたい

LinuxC言語では「タイムアウトを設定してsystem関数を使いたい」と思っても、これが思うように出来ません。

「プロセスをforkさせて、子プロセスでsystem関数を実行して、親プロセスがタイムアウト時に子プロセスを殺せば良い」と思うかも知れませんが、それではうまく行きません。子プロセスを殺しても、そのときsystem関数で実行中のプロセスはinitプロセスの直下に移動してプロセスの実行を続けてしまうからです。

そこで、子プロセスでsystem関数ではなくexecv族の関数を使うようにします。そうすればタイムアウト時に親プロセスが子プロセスを殺すと、execv族の関数によって実行されているシェル関数も殺されます。

これを実装する関数を作りました。使い方は簡単。


int system_with_timeout (char *command, int timeout);

commandにはsystem関数と同じようにコマンドを文字列で渡します。timeoutはタイムアウト値を秒数で渡します。返り値はsystem関数を実行した返り値と同じ値が返ります。指定されたタイムアウト値が経過してもコマンドが終了しなかった場合は、コマンドを強制的に終了します。

以下がsystem_with_timeout()の実装です。


#include
#include
#include
#include
#include
#include

/* タイムアウト時に呼ばれるシグナルハンドラ */
void static system_timeout(int sig);
static jmp_buf env;

int system_with_timeout(const char *cmd, int timeout)
{
 int rtn, status, exit_code;
 static int child_pid;
 char *cmd_for_execv[] = {"/bin/bash", "-c", NULL, NULL};

 cmd_for_execv[2] = (char *) cmd;

 if(sigsetjmp(env, 1)) {
  /* タイムアウト */
  alarm(0);
  signal(SIGALRM, SIG_DFL);
  if(child_pid > 0) {
   rtn = kill(child_pid, SIGKILL);
   fprintf(stderr, "KILLED by TIMEOUT\n");
  }
  return -1;
 }

 child_pid = fork();

 switch(child_pid)
  {
  case -1:
   fprintf(stderr, "ERROR! fork() failed\n");
   return -2;

  case 0:
   /* 子プロセス */
   rtn = execv(cmd_for_execv[0], cmd_for_execv);
   exit(99);
   break;

  default:
   /* 親プロセス */
   /* アラームとシグナルハンドラをセット */
   alarm(timeout);
   signal(SIGALRM, system_timeout);

   /* 子プロセスの終了を待つ */
   child_pid = waitpid(child_pid, &status, WUNTRACED);
   exit_code = WEXITSTATUS(status);
  }

 /* セットしたアラームとシグナルハンドラをクリア */
 alarm(0);
 signal(SIGALRM, SIG_DFL);

 return exit_code;
}

void static system_timeout(int sig)
{
 siglongjmp(env, 1);
}

簡単に説明すると、fork()して、子プロセスがexecvによってコマンドを実行します。親プロセスは子プロセスが実行したコマンドの返り値をWEXITSTATUS関数で受け取って終了します。タイムアウトが発生した場合は、シグナルハンドラsystem_timeout 関数の中のsiglongjmp関数によってsetjmpへ戻り、子プロセスを殺して終了します。

この関数に著作権はありません。自由に利用および再配布して構いません。ただし、この関数を実行して発生した不具合には責任が持てませんが。。。

この関数、かなり使い勝手が良いと思います。使った人は良ければ一報ください。とても嬉しいです。