2

popen()を使用してパイプを作成していますが、プロセスはサードパーティのツールを呼び出していますが、まれに終了する必要があります。

::popen(thirdPartyCommand.c_str(), "w");

例外をスローしてスタックをアンワインドすると、アンワインドはサードパーティのプロセスでpclose()を呼び出そうとしますが、その結果は不要になります。ただし、pclose()は、Centos 4で次のスタックトレースでブロックされるため、返されません。

#0  0xffffe410 in __kernel_vsyscall ()
#1  0x00807dc3 in __waitpid_nocancel () from /lib/libc.so.6
#2  0x007d0abe in _IO_proc_close@@GLIBC_2.1 () from /lib/libc.so.6
#3  0x007daf38 in _IO_new_file_close_it () from /lib/libc.so.6
#4  0x007cec6e in fclose@@GLIBC_2.1 () from /lib/libc.so.6
#5  0x007d6cfd in pclose@@GLIBC_2.1 () from /lib/libc.so.6

pclose()の呼び出しを強制的に成功させてから呼び出す方法はありますか?プログラムで、pclose()が成功するのを待ってプロセスがハングアップするのを回避できます。これは、 popen()されたプロセスとその作業を破棄したいですか?

ファイルを閉じる前に、なんらかの方法でファイルの終わりをpopen()されたファイル記述子に書き込む必要がありますか?

サードパーティのソフトウェアがそれ自体をフォークしていることに注意してください。pclose()がハングした時点で、4つのプロセスがあり、そのうちの1つは機能していません。

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
abc       6870  0.0  0.0   8696   972 ?        S    04:39   0:00 sh -c /usr/local/bin/third_party /home/arg1 /home/arg2 2>&1
abc       6871  0.0  0.0  10172  4296 ?        S    04:39   0:00 /usr/local/bin/third_party /home/arg1 /home/arg2
abc       6874 99.8  0.0  10180  1604 ?        R    04:39 141:44 /usr/local/bin/third_party /home/arg1 /home/arg2
abc       6875  0.0  0.0      0     0 ?        Z    04:39   0:00 [third_party] <defunct>
4

3 に答える 3

2

ここに2つの解決策があります。

  • きちんとしたもの:あなたfork()pipe()そしてexecve()(またはexecもちろん家族の何でも...)「手動で」、そしてあなたがあなたの子供をゾンビにさせたいかどうかを決めるのはあなた次第です。(つまりwait()、彼らのためかどうか)
  • 醜いもの:この子プロセスの1つだけが常に実行されていることが確実な場合は、... yuksysctl()を呼び出す前に、この名前で実行されているプロセスがあるかどうかを確認できます。pclose()

ここでは、きちんとした方法を強くお勧めします。または、サードパーティのツールでその無限ループを修正する責任がある人に尋ねることもできます。

幸運を!

編集:

あなたの最初の質問のために:私は知りません。shoudを使用して名前でプロセスを見つける方法についてsysctl()いくつかの調査を行うと、知っておくべきことがわかります。私自身、これまでにそれを推し進めたことはありません。

2番目と3番目の質問の場合:基本的には+++popen()のラッパーです。fork()pipe()dup2()execl()

fork()プロセスを複製しexecl()、複製されたプロセスのイメージを新しいものに置き換え、プロセスpipe()間通信を処理dup2()し、出力をリダイレクトするために使用されます...そして、複製pclose()wait()れたプロセスが終了するため、ここにいます。

詳細を知りたい場合は、この回答を確認してください。最近、標準のIPCで単純なフォークを実行する方法を説明しました。dup2()この場合、標準出力をパイプにリダイレクトするために使用する必要があるため、少し複雑になります。

popen()/pclose()もちろんオープンソースであるため、ソースコードも確認する必要があります。

最後に、簡単な例を示します。それ以上に明確にすることはできません。

int    pipefd[2];

pipe(pipefd); 
if (fork() == 0) // I'm the child
{
    close(pipefd[0]);    // I'm not going to read from this pipe
    dup2(pipefd[1], 1);  // redirect standard output to the pipe
    close(pipefd[1]);    // it has been duplicated, close it as we don't need it anymore
    execve()/execl()/execsomething()... // execute the program you want
}
else // I'm the parent
{
    close(pipefd[1]);  // I'm not going to write to this pipe
    while (read(pipefd[0], &buf, 1) > 0) // read while EOF
        write(1, &buf, 1);
    close(pipefd[1]);  // cleaning
}

そしていつものように、manページを読み、すべての戻り値を確認することを忘れないでください。

もう一度、頑張ってください!

于 2013-01-06T15:52:06.110 に答える
1

別の解決策は、すべての子供を殺すことです。あなたが持っている唯一の子プロセスがあなたがやったときに開始されるプロセスであることがわかっているならpopen()、それは十分に簡単です。それ以外の場合は、さらに作業が必要になるか、fork()+execve()コンボを使用する必要があります。その場合、最初の子のPIDがわかります。

子プロセスを実行するときはいつでも、そのPPID(親プロセスID)は独自のPIDです。現在実行中のプロセスのリストを読んで、それらを持っているプロセスを収集するのは簡単PPID = getpid()です。ループを繰り返して、PPIDが子のPIDの1つと等しいプロセスを探します。最後に、子プロセスのツリー全体を構築します。

子プロセスは他の子プロセスを作成してしまう可能性があるため、安全を確保するために、を送信してこれらのプロセスをブロックSIGSTOPする必要があります。そうすれば、彼らは新しい子供を作るのをやめます。私の知る限り、あなたはSIGSTOPその行為を阻止することはできません。

したがって、プロセスは次のとおりです。

function kill_all_children()
{
  std::vector<pid_t> me_and_children;

  me_and_children.push_back(getpid());

  bool found_child = false;
  do
  {
    found_child = false;
    std::vector<process> processes(get_processes());
    for(auto p : processes)
    {
      // i.e. if I'm the child of any one of those processes
      if(std::find(me_and_children.begin(),
                   me_and_children.end(),
                   p.ppid()))
      {
         kill(p.pid(), SIGSTOP);
         me_and_children.push_back(p.pid());
         found_child = true;
      }
    }
  }
  while(found_child);

  for(auto c : me_and_children)
  {
    // ignore ourselves
    if(c == getpid())
    {
      continue;
    }
    kill(c, SIGTERM);
    kill(c, SIGCONT);  // make sure it continues now
  }
}

ただし、データを処理するためにコマンド時間を許可する必要があるため、これはおそらくパイプを閉じるための最良の方法ではありません。したがって、必要なのは、タイムアウト後にのみそのコードを実行することです。したがって、通常のコードは次のようになります。

void send_data(...)
{
  signal(SIGALRM, handle_alarm);
  f = popen("command", "w");
  // do some work...
  alarm(60);  // give it a minute
  pclose(f);
  alarm(0);   // remove alarm
}

void handle_alarm()
{
  kill_all_children();
}

--について、alarm(60);場所はpopen()あなた次第です、またはそれ以降の作業も失敗する可能性があることを恐れている場合は、前に配置することもできますpopen()(つまり、パイプがいっぱいになる問題が発生しましたが、私はしません」pclose()子プロセスが永久にループするため、tに到達することさえあります。)

alarm()は世界で最高のアイデアではないかもしれないことに注意してください。必要に応じてウェイクアップできる、poll()またはfdで作成されたスリープのスレッドを使用することをお勧めします。select()そうすれば、スレッドはkill_all_children()スリープ後に関数を呼び出しますが、メッセージを送信して早期にウェイクアップし、pclose()期待どおりに発生したことを通知できます。

注:私get_processes()はこの回答から実装を除外しました。/procあなたはそれを図書館から、または図書館で読むことができますlibprocps。私のsnapwebsitesプロジェクトそのような実装があります。それはと呼ばれます。あなたはそのクラスを刈り取ることができます。process_list

于 2018-08-23T07:44:42.110 に答える
0

私はpopen()を使用して、stdinまたはstdoutを必要としない子プロセスを呼び出しています。このプロセスは、作業を実行するために短時間実行され、その後、すべて自動的に停止します。間違いなく、このタイプの子プロセスの呼び出しは、system()を使用して行う必要がありますか?とにかく、pclose()は、子プロセスが正常に終了したことを確認するために後で使用されます。

特定の条件下では、この子プロセスは無期限に実行され続けます。pclose()は永久にブロックされるため、親プロセスもスタックします。CPU使用率が100%になり、他の実行可能ファイルが不足し、組み込みシステム全体が崩壊します。私は解決策を探してここに来ました。

@cmcによる解決策1 :popen()をfork()、pipe()、dup2()、およびexecl()に分解します。個人的な好みの問題かもしれませんが、私は完全に細かいシステムコールを自分で書き直すのは気が進まないです。私はただ新しいバグを導入することになります。

@cmcによる解決策2 :子プロセスが実際にsysctl()で存在することを確認し、pclose()が正常に返されることを確認します。これは、OP @WilliamKFの問題をなんとか回避していることがわかりました。確かに子プロセスがあり、応答しなくなっただけです。pclose()呼び出しを省略しても、それは解決されません。[余談ですが、@ cmcがこの回答を書いてから7年間で、sysctl()は非推奨になっているようです。]

@Alexis Wilkeによるソリューション3 :子プロセスを強制終了します。私はこのアプローチが一番好きです。それは基本的に、私が手動でステップインして、死にかけている組み込みシステムを蘇生させたときに行ったことを自動化します。popen()を頑固に順守することの問題は、子プロセスからPIDを取得できないことです。私は無駄にしようとしてきました

waitid(P_PGID, getpgrp(), &child_info, WNOHANG);

しかし、DebianLinux4.19システムに乗るのはEINVALだけです。

これが私が一緒に石畳にしたものです。名前で子プロセスを検索しています。この名前のプロセスは1つしかないと確信しているので、いくつかのショートカットを使用する余裕があります。皮肉なことに、コマンドラインユーティリティpsはさらに別のpopen()によって呼び出されます。これはエレガンス賞を獲得することはありませんが、少なくとも私の組み込みシステムは今も浮かんでいます。

FILE* child = popen("child", "r");
if (child)
{
    int nr_loops;
    int child_pid;
    for (nr_loops=10; nr_loops; nr_loops--)
    {
        FILE* ps = popen("ps | grep child | grep -v grep | grep -v \"sh -c \" | sed \'s/^ *//\' | sed \'s/ .*$//\'", "r");
        child_pid = 0;
        int found = fscanf(ps, "%d", &child_pid);
        pclose(ps);
        if (found != 1)
            // The child process is no longer running, no risk of blocking pclose()
            break;
        syslog(LOG_WARNING, "child running PID %d", child_pid);
        usleep(1000000); // 1 second
    }
    if (!nr_loops)
    {
        // Time to kill this runaway child
        syslog(LOG_ERR, "killing PID %d", child_pid);
        kill(child_pid, SIGTERM);
    }
    pclose(child); // Even after it had to be killed
} /* if (child) */

私は難しい方法で、すべてのpopen()をpclose()とペアにする必要があることを学びました。そうしないと、ゾンビプロセスを積み上げてしまいます。これが直接殺害の後に必要になることは注目に値します。これは、マンページによると、popen()が実際に子プロセスを含むsh -cを起動し、この周囲のshがゾンビになるためだと思います。

于 2020-07-24T10:23:55.423 に答える