c++ - 调用 posix_spawn 时关闭所有文件句柄

标签 c++ c linux file posix

我想使用 posix_spawn(...) (或非常类似的东西)生成一组进程。此函数接受类型为 posix_spawn_file_actions_t 的参数,它允许我指定应如何处理打开的文件句柄。从我可以确定的the documentation ,所有文件都是从调用进程继承并根据posix_spawn_file_actions_t结构中的信息修改的。

我希望生成的进程未打开所有文件(stdin、stdout 和 stderr 除外)。有谁知道如何做到这一点?显然,这可以在某些实现中使用“POSIX_SPAWN_CLOEXEC_DEFAULT”生成属性标志来完成,但这在我的平台上不可用。我也可以使用 fcntl(...)每次打开文件时都指定“执行时关闭”,但我觉得更适合解决此问题的本地化解决方案。

最佳答案

打开文件描述符处理fork()exec*()在多线程应用程序中使用文件租约和/或 fcntl() 锁(记录锁)是冒险的。

一般来说,O_CLOEXEC/fcntl(fd, F_SETFD, FD_CLOEXEC)选项优于显式关闭描述符,因为显式关闭描述符有一些不良副作用。特别是,如果您对描述符有租约,关闭子进程中的描述符将释放租约。

请注意,在 Linux 中,fcntl() 锁不会在 fork() 中继承;参见 man 2 fork 中的描述.

posix_spawn()在C库中实现,文件操作可以通过posix_spawn_file_actions_init()进行管理, posix_spawn_file_actions_addclose()等等;查看手册页中的另请参阅 列表。就我个人而言,我不会使用此接口(interface),因为在 exec*() 之前关闭子进程中的描述符至少同样简单。

由于以上所有原因,我个人更喜欢使用 O_CLOEXEC 和/或使用 fcntl(fd,F_SETFD,FD_CLOEXEC) 打开文件,以便关闭所有描述符-默认情况下执行。有点像

#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/resource.h>

void set_all_close_on_exec(void)
{
    struct rlimit  rlim;
    long           max;
    int            fd;

    /* Resource limit? */
#if defined(RLIMIT_NOFILE)
    if (getrlimit(RLIMIT_NOFILE, &rlim) != 0)
        rlim.rlim_max = 0;
#elif defined(RLIMIT_OFILE)
    if (getrlimit(RLIMIT_OFILE, &rlim) != 0)
        rlim.rlim_max = 0;
#else
    /* POSIX: 8 message queues, 20 files, 8 streams */
    rlim.rlim_max = 36;
#endif

    /* Configured limit? */
#if defined(_SC_OPEN_MAX)
    max = sysconf(_SC_OPEN_MAX);
#else
    max = 36L;
#endif

    /* Use the bigger of the two. */
    if ((int)max > (int)rlim.rlim_max)
        fd = max;
    else
        fd = rlim.rlim_max;

    while (fd-->0)
        if (fd != STDIN_FILENO  &&
            fd != STDOUT_FILENO &&
            fd != STDERR_FILENO)
            fcntl(fd, F_SETFD, FD_CLOEXEC);
}

是一种非常便携的方法,可以快速将所有打开的描述符(标准描述符除外)设置为 close-on-exec;库有时会在内部使用描述符,并且可能不会设置 O_CLOEXEC。在我的系统上,set_all_close_on_exec() 运行需要 0.25 毫秒;最大值分别为 4096 和 1024,因此它最终尝试设置 4093 个文件描述符。

(注意 fcntl(fd,F_SETFD,FD_CLOEXEC) 应该对所有有效的描述符成功,而对其他(无效/未使用的)描述符失败并返回 errno==EBADF .)

请注意,简单地尝试在所有可能的描述符上设置标志比尝试找出哪些描述符实际打开要快得多。 (后者在 Linux 中是可能的,例如 /proc/self/fd/。)

其次,我更喜欢使用辅助函数来创建到子进程的控制管道,将文件描述符移动到适当的位置(这并不总是微不足道的),并派生子进程。签名通常类似于

int do_exec(pid_t *const childptr,
            const char *const cmd,
            const char *const args[],
            const int stdin_fd,
            const int stdout_fd,
            const int stderr_fd);

我的 do_exec() 函数创建了一个 close-on-exec 控制管道,以区分执行子二进制文件失败和子二进制文件退出状态。 (如果子进程 exec() 失败,它会将 errno 作为有符号字符写入控制管道。父进程尝试从另一个进程读取单个有符号字符控制管道的末端。如果成功,则 exec 失败;父级使用例如 waitpid() 获取子级,并返回 errno 错误。否则,由于 exec( ), 所以父进程知道子进程已经开始执行,并且可以关闭(最后打开的)控制管道。)

最后,如果你有一个多线程服务器类型的进程需要以最小的延迟和资源使用产生新的子进程,启动一个连接到带有 Unix 域套接字的原始进程的子进程(因为你可以使用辅助消息使用这些传输凭据和文件描述符),并让该子进程启动实际的子进程。这正是例如Apache mod_cgid 和大多数 FastCGI 实现都可以。

关于c++ - 调用 posix_spawn 时关闭所有文件句柄,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21950549/

相关文章:

c - 请解释此代码如何去除图像中的噪声

c - Wait_event_interruptible 与wake_up_interruptible

linux - 如果我想实现 RDMA NIC 驱动程序,从哪里开始 - 建议

C++:如何检查 cin 缓冲区是否为空?

C++ 初学者逻辑错误 - 返回 0

c - MPI 基本示例不起作用

c - TCP sendto 返回长度但服务器没有收到任何东西

C++比较字符串和字符

c++ - 'n' boost::thread 实例执行 'm' 个作业

linux - Gnu 汇编程序 .data 部分值在系统调用后损坏