从POSIX线程开始使用C++打开/ proc/net/tcp会在大多数情况下失败

Mik*_*ell 7 c++ linux pthreads

当我尝试从C++中的子POSIX线程打开/ proc/net/tcp时,它失败并显示"No such file or directory"错误.如果我尝试从父线程打开它,它每次成功,并且在父线程中打开/关闭它的过程然后使它在子线程中的大约三分之一的时间也成功.我可以100%的时间在子线程中打开/ proc/uptime而不会出现问题.这是一些可以使用"g ++ -Wall test.cc -o test -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);
}
Run Code Online (Sandbox Code Playgroud)

我在Linux内核3.2.0上使用Intel i5-2520M(2核*2个虚拟内核)的Ubuntu 12.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$
Run Code Online (Sandbox Code Playgroud)

值得注意的是,如果我使用fork而不是posix线程,我就没有这个问题.如果我使用fork,那么子进程在读取/ proc/net/tcp时没有问题

只需要输入几个数据点....看起来这是Linux中的回归,因为2.6.35似乎在100%的时间都可以工作.3.2.0 pukes大多数时候甚至在我的慢速旧Pentium M笔记本电脑上.

jxh*_*jxh 2

这种行为似乎是虚拟文件系统中的一种错误/proc如果您在打开文件之前添加此代码:

    system("ls -l /proc/net /proc/self/net/tcp");
Run Code Online (Sandbox Code Playgroud)

您将看到这/proc/net是一个到 的符号链接/proc/self/net,并且/proc/sec/net/tcp对于对 的两次调用都正确列出open_test,即使生成的线程调用失败也是如此。

编辑:我刚刚意识到上面的测试是假的,因为 self 指的是系统调用的 shell 进程,而不是这个进程。使用以下函数也可以揭示该错误:

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());
}
Run Code Online (Sandbox Code Playgroud)

您会发现生成的线程有时无法看到父级的/net/tcp文件。事实上它已经消失了,因为这是运行该ls命令的生成 shell 进程。

下面的解决方法允许子线程可靠地访问其/proc/net/tcp.

我的理论是,这是某种竞争条件错误,正确地将/proc/self线程条目设置为父状态和线程特定状态的正确混合。作为测试和解决方法,我修改了代码open_test以使用与线程关联的“进程标识符”,而不是尝试访问父进程的(因为/proc/self指的是父进程 ID,而不是线程的)。

编辑:正如证据表明的那样,该错误与父进程在/proc/self/...子线程有机会读取其状态之前清理其状态有关。我仍然认为这是一个错误,因为子线程在技术上仍然是进程的一部分。getpid()主线程调用前后还是一样的pthread_exit()/proc父进程的条目应保持有效,直到所有子线程完成为止。虽然

Edit2:乔纳斯认为这可能不是一个错误。作为证据,有以下内容man proc

       /proc/[pid]/fd
              ...
              在多线程进程中,该目录的内容是
              如果主线程已经终止则不可用(通常是
              通过调用 pthread_exit(3)) 进行联盟。

/proc/self但是,请在同一页面条目中考虑此条目man

       /进程/自身
              该目录指的是访问/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;
}
Run Code Online (Sandbox Code Playgroud)