我想使用posix_spawn(...)(或类似的东西)生成一系列进程。此函数接受posix_spawn_file_actions_t类型的参数,该参数允许我指定应如何处理打开的文件句柄。根据我从the documentation确定的内容,所有文件都从调用进程继承,并根据posix_spawn_file_actions_t结构中的信息进行修改。
我希望所有文件都不被生成的进程打开(stdin,stdout和stderr除外)。有谁知道如何做到这一点?显然,这可以通过'POSIX_SPAWN_CLOEXEC_DEFAULT'spawn属性标志在某些实现上完成,但这在我的平台上不可用。每当我打开文件时,我也可以使用fcntl(...)指定'关闭exec',但我觉得这个问题的更局部解决方案更可取。
答案 0 :(得分:7)
使用文件租约和/或fcntl()
锁(记录锁)在多线程应用程序中通过fork()
和exec*()
打开文件描述符处理是很冒险的。
通常,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)
,以便默认情况下所有描述符都是close-on-exec。像
#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
失败。)
请注意,简单地尝试在所有可能的描述符上设置标志要快得多,而不是尝试找出实际打开的描述符。 (后者可以通过例如/proc/self/fd/
在Linux中实现。)
其次,我更喜欢使用辅助函数来创建子进程的控制管道,将文件描述符移动到适当的位置(这并不总是微不足道的),并分叉子进程。签名通常类似于
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实现都可以。
答案 1 :(得分:2)
在创建新进程之前,在所有打开的文件描述符上设置FD_CLOEXEC
(using fcntl()
),或者使用open()
set the O_CLOEXEC
标记。
来自posix_spawn() specification:
如果file_actions是空指针,那么在调用进程中打开的文件描述符应在子进程中保持打开状态,除了那些设置了close-on-exec标志FD_CLOEXEC的文件描述符(参见fcntl())。