7

当我尝试从 C++ 中的子 POSIX 线程打开 /proc/net/tcp 时,它失败并出现“没有这样的文件或目录”错误。如果我尝试从父线程打开它,它每次都会成功,并且在父线程中打开/关闭它的过程也会使它在子线程中成功大约三分之一的时间。我可以在 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);
}

我在 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$

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

只需输入几个数据点......看起来这是 Linux 中的回归,因为 2.6.35 似乎 100% 的时间都在工作。3.2.0 大部分时间都在呕吐,即使在我基于 Pentium M 的旧笔记本电脑上也是如此。

4

3 回答 3

4

正如斯科特在他的回答中指出的那样,添加 a 可以pthread_join(thread, NULL)修复症状。但为什么?

让我们把程序放在 gdb 中,并在打开失败的地方设置一个断点:

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

然后我们可以观察到两种不同类型的行为:

  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

如果在 pthread_exit() 调用之前添加 pthread_join(thread, NULL) 调用,您的程序将正常工作。

于 2012-07-20T20:29:18.967 回答
2

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

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

您将看到它/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());
}

您会看到生成的线程有时无法看到父母的/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文件的进程
              系统,并且与 /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 回答