2017-10-11 18 views
1

spawn()で特定のプロセスをフォークするときに、次のコードがread(fds[0]...)spawn()にブロックすることがあります。pipe2の代わりにパイプを使用するとfork後にパイプ上のブロックを読み取る

#include <fcntl.h> 
#include <unistd.h> 

#include <atomic> 
#include <mutex> 
#include <thread> 
#include <vector> 

void spawn() 
{ 
    static std::mutex m; 
    static std::atomic<int> displayNumber{30000}; 

    std::string display{":" + std::to_string(displayNumber++)}; 
    const char* const args[] = {"NullXServer", display.c_str(), nullptr}; 

    int fds[2]; 

    m.lock(); 
    pipe(fds); 
    int oldFlags = fcntl(fds[0], F_GETFD); 
    fcntl(fds[0], F_SETFD, oldFlags | FD_CLOEXEC); 
    oldFlags = fcntl(fds[1], F_GETFD); 
    fcntl(fds[1], F_SETFD, oldFlags | FD_CLOEXEC); 
    m.unlock(); 

    if (vfork() == 0) { 
    execvp("NullXServer", const_cast<char**>(args)); 
    _exit(0); 
    } 

    close(fds[1]); 
    int i; 
    read(fds[0], &i, sizeof(int)); 
    close(fds[0]); 
} 

int main() 
{ 
    std::vector<std::thread> threads; 
    for (int i = 0; i < 100; ++i) { 
    threads.emplace_back(spawn); 
    } 

    for (auto& t : threads) { 
    t.join(); 
    } 

    return 0; 
} 

注:ここでパイプを作るのは役に立たない。デッドロックを実証するためだけに行われます。 read(fds[0], ...)spawn()は決してブロックするべきではありません。 readが呼び出されるとパイプの書き込み終了はすべて終了し、すぐにreadに戻ります。親プロセスのパイプの書き込み終了は明示的に閉じられ、ファイル記述子にFD_CLOEXECフラグが設定されているため、子プロセスの書き込み終了は暗黙的に閉じられ、execvpが成功するとすぐにファイル記述子を閉じますこの場合は常にそうです)。

ここでの問題は、私がread()をしばらくブロックしていることです。

m.lock(); 
pipe(fds); 
int oldFlags = fcntl(fds[0], F_GETFD); 
fcntl(fds[0], F_SETFD, oldFlags | FD_CLOEXEC); 
oldFlags = fcntl(fds[1], F_GETFD); 
fcntl(fds[1], F_SETFD, oldFlags | FD_CLOEXEC); 
m.unlock(); 

:コードの両方の部分が、少なくともFD_CLOEXECがパイプファイルディスクリプタのためにアトミックに設定さをもたらすはずであるにもかかわらず

pipe2(fds, O_CLOEXEC); 

修正ブロック読み出しの全てを交換

残念ながら、私が配備するすべてのプラットフォームでpipe2を利用することはできません。

が上記のコードでpipeのアプローチを使用してブロックされる理由を誰かが明らかにすることはできますか?

いくつかのより多くの観測:vfork()ブロックをカバーするためにmutexロックを拡張

  • 読み取りブロッキングを解決します。
  • システムコールが1つも失敗しません。
  • vfork()の代わりにfork()を使用すると同じ動作を示します。
  • 産卵プロセスは重要です。この場合、 'null' Xサーバープロセスが特定のディスプレイに生成されます。例えば、ここでの「ls」のフォークはブロックされないか、ブロックが発生する可能性はかなり低くなります。わかりません。
  • Linux 2.6.18から4.12.8まで再現可能ですので、これはLinuxカーネルの問題ではありません。
  • GCC 4.8.2とGCC 7.2.0の両方を使用して再現可能です。

答えて

1

この理由は、あなたがファイルディスクリプタのフラグを設定する直前に、その別のスレッド

// Thread A int fds[2]; m.lock(); pipe(fds); 

あなたがここにパイプを作成
後かもしれないだけ vfork()とexec

// Thread B 
if (vfork() == 0) { 
    execvp("NullXServer", const_cast<char**>(args)); 
    _exit(0); 
} 

です:

// Thread A again... 
int oldFlags = fcntl(fds[0], F_GETFD); 
fcntl(fds[0], F_SETFD, oldFlags | FD_CLOEXEC); 
oldFlags = fcntl(fds[1], F_GETFD); 
fcntl(fds[1], F_SETFD, oldFlags | FD_CLOEXEC); 
m.unlock(); 

となり、Bの子プロセスはスレッドAによって作成されたファイル記述子を継承します。

vfork()/execvp()を含むようにmutexを拡張すると、この効果が移行されるはずです。

m.lock(); 
pipe(fds); 
int oldFlags = fcntl(fds[0], F_GETFD); 
fcntl(fds[0], F_SETFD, oldFlags | FD_CLOEXEC); 
oldFlags = fcntl(fds[1], F_GETFD); 
fcntl(fds[1], F_SETFD, oldFlags | FD_CLOEXEC); 

if (vfork() == 0) { 
    execvp("NullXServer", const_cast<char**>(args)); 
    _exit(0); 
} 
m.unlock(); 
+0

ありがとうございます。私は何らかの理由で持っていた印象のようなファイル記述子フラグを設定している間に実際には1つのスレッドしか実行されないことを保証するものではありません。 –

+0

@TonvandenHeuvel恥ずかしい必要はありません、並行コードはあまりにもしばしば正しく考えるためのピタです;) – Ctx

関連する問題