线程和fork: fgetc()在读取popen()-pipe时阻塞

Threads and fork: fgetc() blocks when reading from popen()-pipe

本文关键字:-pipe popen 读取 fgetc 线程 fork      更新时间:2023-10-16

在一个多线程程序(运行在ARM上)我有

一个主线程,它定期检查popen( "pidof -s prog" )是否有另一个程序正在运行。我使用O_CLOEXEC标志作为文件描述符,并检查fgetc()是否从管道接收任何内容。将文件描述符设置为"non-blocking"将导致没有任何内容被读取,并且对没有帮助。与shell命令相同的pidof命令执行良好。

在另一个线程中,fork()与子进程中的execl()一起用于在特定事件发生时启动rsync操作。父进程使用一个信号处理程序来观察子进程的状态,并且可以选择在另一个特定的事件中终止子进程。不管我是用rsync还是sleep调用exec(),结果都是一样的。

问题是主线程中的fgetc()阻塞直到子进程终止。

我将尝试通过早期fork()来解决这个问题(在应用程序是单线程的时候,就像我开始的另一篇文章中假设的那样)。

但无论如何:

我想了解从管道读取时导致fgetc()阻塞的原因。

到目前为止,我尝试了一些事情:

  • 我试图用一个小的示例应用程序重现这个问题,它做了我上面描述的,并希望它会显示相同的错误行为,但不幸的是它工作得很好,这就是为什么我没有在这里提供任何代码。也许我没有抓住重点。
  • 通过system()使用相同的rsync调用不会导致任何问题
  • 我已经看了system()的实现,可以看到在fork() ing之前信号被操纵:

    • SIGCHLD被阻塞
    • SIGINT和SIGQUIT被忽略

    我需要SIGCHLD的信号处理程序,但出于好奇,我试图做同样的代码从上面(我用pthread_sigmask()代替sigprocmask()) -没有任何成功,行为保持不变。

    我无法在我的BSP提供的源代码中找到任何system()的实现。

程序通过fstream打开其他文件-没有O_CLOEXEC(更改它会有点麻烦)

修复和解释意外行为

的确,我错过了相关的一点。在将示例程序更适应于原始代码示例之后,我发现信号处理程序(在测试程序中工作)是问题所在。摘录:

void MyClass::sig_handler(int sig) {
    if( m_pid < 1 ) // not the child we're waiting for
        return;
    pid_t pid;
    int wstatus;
    while ((pid = waitpid( -1, &wstatus, WNOHANG )) != -1 ) {
        // error: this returns 0 as long as any children are alive
        // -> check for "> 0" to ignore active child processes
        if( pid != m_pid )
            return;
        // handle stuff here...
    }
}

我必须替换下面的行

while ((pid = waitpid( -1, &wstatus, WNOHANG )) != -1 )

while ((pid = waitpid( -1, &wstatus, WNOHANG )) > 0 )

因为程序的其他线程fork()子线程(例如与popen()一起)。如果这些终止,也调用信号处理程序(静态类函数)。

如我所知:

在我调用fork()的线程中,我使用具有默认值和重置值-1的成员m_pid。它从fork()获取pid。如果m_pid为-1,信号处理程序立即返回。

程序在popen()被阻塞,fork()被阻塞(可以是fork()被阻塞的任何其他调用),因此当popen()返回时进入SIGCHLD的信号处理程序。对m_pid的检查通过,因为m_pid = fork()已经被调用。waitpid()不返回-1,但popen()子进程的pid,然后继续检查返回值= 0,直到所有子进程都终止-我正在等待的那个仍然活着!只有当waitpid()返回-1时,主线程才能继续读取fgetc()

手册页来自waitpid:

如果指定了WNOHANG,并且pid指定了一个或多个子(ren)存在,但尚未更改状态,则返回0。错误时,-1返回

因为签名处理程序检查m_pid != -1,所以只有当我在MyClass中使用fork()来设置m_pid时才会出现问题。

这就是为什么使用system()不会引起问题。m_pid没有被设置为值!= -1,因此如果子进程在主线程中被popen()调用,sig处理程序将立即返回。

system()调用的模仿失败,因为我将m_pid设置为fork(),因此信号处理程序没有立即返回。

我猜由于信号处理程序是static member function,处理程序阻塞了fork()创建子进程的线程。