【问题标题】:File Descriptor not closed on exec文件描述符未在 exec 上关闭
【发布时间】:2014-04-06 14:07:15
【问题描述】:

在 exec() 之后,子进程挂在套接字上时遇到问题。该进程 1) 读取 udp 数据包,2) 杀死/启动其他进程。此进程通过它们发送的 udp 数据包监视其他进程。

这在 Windows、Linux 和 AIX 上运行。我在 AIX 上没有遇到任何问题,只有在 Linux 上。 (Windows 代码有很大不同,所以我不会详细说明。)

我在通过 fcntl() 创建返回的描述符后立即在返回的描述符上设置 FD_CLOEXEC 标志。这必须在 Red Hat EL 4-6 上运行,因此在创建时使用 O_CLOEXEC 不是一个选项(RHEL4/5 中的内核没有该选项。)

为了维护,可能需要重新启动监控进程,当我尝试重新启动它时,我发现偶尔有一个子进程仍然绑定到套接字,导致监控进程无法这样做。 [通常这不是问题(因为用户会看到重启失败并采取适当的措施),但是监视器本身通过不同的机制进行监控(以避免 SPOF),并且监控过程的自动重启可能如果其子进程之一持有套接字,则失败。这可能导致下游发生更多坏事。 ]

我什至在 fork() 和 exec() 调用之间添加代码以显式关闭子进程中的套接字(以及相关的关闭),并通过同步 fork() 和 read()一个 pthread_mutex 以便在发生分叉时我不会从套接字读取。

套接字是用

创建的
s = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP )

没有其他选择。创建完成后,我立即调用 fcntl 来设置 FD_CLOEXEC。此时进程仍然是单线程的,因此在设置标志之前没有竞争条件(理论上)。

绑定接下来完成,但仍然是单线程的。它绑定到与 getaddrinfo 返回的“localhost”匹配的第一个 IPV4 地址(可能没有必要,但它使用底层实用函数来简化对绑定的调用。)

fork 之后子进程中的关闭逻辑(由于 FD_CLOEXEC,这些都不是必需的)是:

char retryClose = 1;
int eno = 0;
int retries = 20;

if ( shutdown( s, SHUT_RDWR ) ) {
    /* Failed to shutdown. Wait and try again */
    my_sleep( 3000 ); /* sleep using select(0,NULL,NULL,NULL, timeval) */
    shutdown( socketno, SHUT_RDWR );
    /* not much else can be done... */
}
while ( retryClose && ( close( s ) == -1 ) )
{
    /* save error number */
    eno = errno;
    /* check specific error */
    switch ( eno ) {
        case ( EIO ) :
        /* terminate loop if retries have expired; otherwise sleep for a while and try again */
            if ( --retries <= 0 ) {
                retryClose = 0;
            }
            else {
                my_sleep( 50 );
            }
            case ( EINTR ) :
                break;
            case ( EBADF ) :
        default:
            retryClose = FALSE;
            break;
    } /* switch ( eno ) */
}

所以,我设置了 FD_CLOEXEC 标志,并在 exec() 调用之前明确关闭 fd。

我错过了什么吗?我能做些什么来确保子进程真的不会挂在套接字上吗?

【问题讨论】:

  • 你确定这是一个在套接字上徘徊的子进程吗?看到一些 pre-fork 代码会很有趣,特别是 bind(),但最好是一个完整的可编译示例,它展示了这种行为。您也可以尝试在重启前和重启期间使用lsof -i udp或ss -apeu检查套接字状态。
  • @thouvila netstat -anp 确认它是一个子进程,与 /proc//fd/ inode 交叉引用端口的 /proc/net/udp inode 一样(尽管无论如何,这可能是 netstat 所做的。)杀死子进程也可以解决问题。
  • s 和 socketno 是同一个套接字吗?您检查fcntl() 调用是否成功?您是否显式添加了 FD_CLOEXEC 标志?代码是什么样的?这里真的没什么可做的。我没有遇到过这样的问题,所以我无法仅从您的描述中猜测解决方案。您的应用程序代码可能很复杂,因为您提到了底层实用功能等。也许某些实用功能做了一些不可移植的事情。您真的应该尝试编写一段展示这种行为的代码并将其展示给我们。

标签: c linux sockets


【解决方案1】:

事实证明,导致问题的不是 fork/exec。

在启动所有子进程后,服务器进程可以重新启动几次,没有任何问题,但偶尔,当服务器死机时,其中一个子进程实际上会抢占服务器套接字。

从在客户端使用 connect()/send() 切换到仅使用 sendto() 似乎已经解决了问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-09-05
    • 2018-12-02
    • 2014-04-10
    • 2013-12-11
    • 2011-06-03
    • 1970-01-01
    • 1970-01-01
    • 2014-03-28
    相关资源
    最近更新 更多