在 Linux 系统中,当一个进程(A)打开文件并获取文件描述符(fd)后,如果通过 system() 启动子进程(B),子进程会继承父进程的所有打开文件描述符。这可能导致多个进程同时写入同一个日志文件,造成日志混乱或数据损坏。
书写辅助函数 set_fd_cloexec 为了解决这个问题。它通过设置 FD_CLOEXEC 标志位,确保在执行 exec 系列函数(如 system() 内部调用的 exec)时,文件描述符会被自动关闭,子进程不会继承该文件描述符。
函数解析
c
static void set_fd_cloexec(int fd)
{
if (fd < 0) // 检查无效文件描述符
{
return;
}
int flags = fcntl(fd, F_GETFD); // 获取当前文件描述符标志
if (flags == -1) // 检查获取是否失败
{
return;
}
fcntl(fd, F_SETFD, flags | FD_CLOEXEC); // 设置 FD_CLOEXEC 标志
}
关键点说明
-
FD_CLOEXEC作用设置此标志后,当进程调用
exec系列函数(如system()启动的子进程)时,操作系统会自动关闭该文件描述符,防止子进程继承。 -
函数逻辑
- 先检查
fd是否有效(fd >= 0) - 使用
fcntl(fd, F_GETFD)获取当前标志 - 通过位或操作
flags | FD_CLOEXEC添加标志 - 用
fcntl(fd, F_SETFD, ...)应用新标志
- 先检查
-
使用场景
在打开文件后立即调用此函数:
cint log_fd = open("app.log", O_WRONLY | O_CREAT | O_APPEND, 0644); if (log_fd >= 0) { set_fd_cloexec(log_fd); // 确保子进程不继承此 fd }
注意事项
-
错误处理
当前函数忽略了
fcntl的失败情况,实际应用中建议添加错误日志:cif (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { perror("fcntl(F_SETFD) failed"); } -
替代方案
-
在打开文件时直接设置标志(更推荐):
cint fd = open("file.txt", O_RDWR | O_CLOEXEC, 0644); -
Linux 专有函数:
cint fd = open("file.txt", O_RDWR | O_CLOEXEC, 0644);
-
-
继承例外
若需要特定文件描述符被子进程继承(如标准输入/输出),则不应设置
FD_CLOEXEC。
总结
通过正确设置 FD_CLOEXEC 标志,可确保:
- 父进程(A)正常使用文件描述符
- 子进程(B)启动时自动关闭该描述符
- 避免多进程同时写文件导致的冲突