文件描述符未在exec上关闭

时间:2014-03-04 20:19:29

标签: c linux sockets

我遇到了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,以便在发生fork时我不会从套接字读取。

使用

创建套接字
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。

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

1 个答案:

答案 0 :(得分:0)

原来,导致问题的不是fork / exec。

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

从使用客户端中的connect()/ send()切换到sendto()似乎解决了这个问题。