7

C++で子POSIXスレッドから/proc/ net / tcpを開こうとすると、「そのようなファイルまたはディレクトリはありません」というエラーで失敗します。親スレッドから開こうとすると毎回成功し、親スレッドで開閉するプロセスで子スレッドでも約3分の1の確率で成功します。子スレッドで/proc/ uptimeを100%問題なく開くことができます。「g++-Wall test.cc-otest-pthread」でコンパイルできるサンプルコードを次に示します。

#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <pthread.h>

using namespace std;

void * open_test (void *)
{
    ifstream in;
    in.open("/proc/net/tcp");
    if (in.fail())
        cout << "Failed - " << strerror(errno) << endl;
    else
        cout << "Succeeded" << endl;
    in.close();

    return 0;
}

int main (int argc, char * argv[])
{
    open_test(NULL);

    pthread_t thread;
    pthread_create(&thread, NULL, open_test, NULL);
    pthread_exit(0);
}

Linuxカーネル3.2.0上のInteli5-2520M(2コア* 2仮想コア)を搭載したUbuntu12.04ボックスでこれを実行しています。上記のコードを6回続けて実行した私の出力は次のとおりです。

mike@ung:/tmp$ ./test
Succeeded
Failed - No such file or directory
mike@ung:/tmp$ ./test
Succeeded
Succeeded
mike@ung:/tmp$ ./test
Succeeded
Failed - No such file or directory
mike@ung:/tmp$ ./test
Succeeded
Failed - No such file or directory
mike@ung:/tmp$ ./test
Succeeded
Succeeded
mike@ung:/tmp$ ./test
Succeeded
Failed - No such file or directory
mike@ung:/tmp$

posixスレッドの代わりにフォークを使用すれば、この問題は発生しないことに注意してください。フォークを使用する場合、子プロセスは/ proc / net/tcpの読み取りに問題はありません。

投入するデータポイントはほんの2、3です。2.6.35は100%動作しているように見えるため、これはLinuxでのリグレッションのようです。3.2.0は、私の遅い古いPentiumMベースのラップトップでもほとんどの場合吐き出します。

4

3 に答える 3

4

スコットが彼の答えで指摘しているように、pthread_join(thread, NULL)修正を追加すると症状が修正されます。しかし、なぜ?

プログラムをgdbに入れて、オープンに失敗したポイントにブレークポイントを設定しましょう。

(gdb) break test.cc:14
Breakpoint 1 at 0x400c98: file test.cc, line 14.

次に、2つの異なるタイプの動作を観察できます。

  1. (gdb) run […]
    Succeeded
    [New Thread 0x7ffff7fd1700 (LWP 18937)]          // <- child thread
    [Thread 0x7ffff7fd3740 (LWP 18934) exited]       // <- parent thread
    [Switching to Thread 0x7ffff7fd1700 (LWP 18937)]
    Breakpoint 1, open_test () at test.cc:14
    
  2. (gdb) run
    Succeeded
    [New Thread 0x7ffff7fd1700 (LWP 19427)]          // <- child thread
    Succeeded
    [Thread 0x7ffff7fd1700 (LWP 19427) exited]
    [Inferior 1 (process 19424) exited normally]
    

最初のものは、親プロセスが子の前に終了することを示唆しています。Linuxの場合と同様に、プロセスとスレッドはほとんど同じです。これは、メインプロセスに関連付けられたPIDがクリーンアップされることを意味します。ただし、子スレッドの実行を妨げるものはありません。それと彼のpidはまだ完全に有効です。/proc/selfそれは、その時点で削除されたメインプロセスのPIDを指しているだけです。

于 2012-07-21T08:13:56.573 に答える
2

/procこの動作は、仮想ファイルシステムの一種のバグのようです。ファイルを開く直前にこのコードを追加する場合:

    system("ls -l /proc/net /proc/self/net/tcp");

/proc/netこれはへのシンボリックリンク/proc/self/netであり、生成されたスレッド呼び出しが失敗した場合でも、/proc/sec/net/tcpへの両方の呼び出しに対して適切にリストされていることがわかります。open_test

編集:自己はこのプロセスではなくシステムコールのシェルプロセスを参照するため、上記のテストは偽物であることに気づきました。代わりに次の関数を使用すると、バグも明らかになります。

void ls_command () {
    ostringstream cmd;
    cmd << "ls -l /proc/net "
        << "/proc/" << getpid()
        << "/net/tcp "
        << "/proc/" << syscall(SYS_gettid)
        << "/net/tcp";
    system(cmd.str().c_str());
}

/net/tcp生成されたスレッドが親のファイルを表示できない場合があることがわかります。これはコマンドを実行しているスポーンされたシェルのプロセスであるため、実際には消えていlsます。

以下の回避策により、子スレッドはそのスレッドに確実にアクセスできます/proc/net/tcp

/proc/self私の理論では、これは、スレッドのエントリを親状態とスレッド固有の状態の適切なブレンドとして正しく設定することによる、ある種の競合状態のバグです。テストと回避策として、open_test親プロセスにアクセスしようとするのではなく、スレッドに関連付けられた「プロセス識別子」を使用するようにコードを変更しました(スレッドではなく/proc/self親プロセスIDを参照するため)。

編集:証拠が示すように、バグは/proc/self/...、子スレッドがそれを読み取る機会を得る前に、親プロセスがその状態をクリーンアップすることに関係しています。子スレッドはまだ技術的にプロセスの一部であるため、私はまだこれをバグとして維持しています。これgetpid()は、メインスレッドがを呼び出す前後で同じpthread_exit()です。親プロセスの/procエントリは、すべての子スレッドが完了するまで有効なままである必要があります。それでも

Edit2: Jonasは、これはバグではないかもしれないと主張しています。その証拠として、これがありますman proc

       / proc / [pid] / fd
              ..。
              マルチスレッドプロセスでは、このディレクトリの内容は次のとおりです。
              メインスレッドがすでに終了している場合は使用できません(通常-
              味方はpthread_exit(3)を呼び出します)。

ただし/proc/self、同じmanページエントリでこのエントリを検討してください。

       / proc / self
              このディレクトリは、/procファイルにアクセスするプロセスを指します
              システムであり、によって指定された/procディレクトリと同じです。
              同じプロセスのプロセスID。

Linuxではスレッドとプロセスが同じように扱われるため、これがバグではないと信じる場合、スレッドは機能することを期待している必要があり/proc/selfます。以下の回避策のように、バージョンが利用できなくなったときに値/proc/selfを使用するように変更することで、バグを簡単に修正できます。/proc/[gettid]/proc/[getpid]

void * open_test (void *)
{
    ifstream in;
    string file = "/proc/net/tcp";
    in.open(file.c_str());
    if (in.fail()) {
        ostringstream ss;
        ss << "/proc/" << syscall(SYS_gettid) << "/net/tcp";
        cout << "Can't access " << file
             << ", using " << ss.str() << " instead" << endl;
        file = ss.str();
        in.open(file.c_str());
    }
    if (in.fail())
        cout << "Failed - " << strerror(errno) << endl;
    else
        cout << "Succeeded" << endl;
    in.close();

    return 0;
}
于 2012-07-20T16:33:25.307 に答える
2

pthread_exit()呼び出しの前にpthread_join(thread、NULL)呼び出しを追加すると、プログラムは正しく機能します。

于 2012-07-20T20:29:18.967 に答える