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

调用posix_spawn时关闭所有文件句柄,第1张

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

使用文件租约和/或锁(记录锁)在多线程应用程序中

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()
等管理。查看手册页中的
另请参阅 列表。就我个人而言,我不会使用此接口,因为在子进程之前关闭描述符
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;    longmax;    int fd;    #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        rlim.rlim_max = 36;#endif    #if defined(_SC_OPEN_MAX)    max = sysconf(_SC_OPEN_MAX);#else    max = 36L;#endif        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);}

这是一种可移植的方法,用于快速将所有打开的描述符(标准描述符除外)设置为执行时关闭;库有时在内部使用描述符,而未设置

O_CLOEXEC
。在我的系统上,
set_all_close_on_exec()
需要0.25ms才能运行;最大值分别为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()
函数创建了一个执行时关闭控制管道,以区分执行子二进制文件的失败状态和子二进制文件退出状态。(如果子进程失败
exec()
,它将
errno
作为签名的char
写入控制管道。父进程尝试从控制管道的另一端读取单个签名的char。如果成功,则exec失败;父进程收获子进程使用例如
waitpid()
,并返回
errno
错误。否则,由于exec()导致管道已关闭,因此父进程知道子进程已开始执行,并可以关闭(控制管道的最后打开端)。

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



欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5015433.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-15
下一篇 2022-11-14

发表评论

登录后才能评论

评论列表(0条)

保存