1. 问题背景:主子进程文件描述符共享的隐患
在Linux环境下开发C/C++程序时,我们经常会遇到一个看似简单却暗藏玄机的问题——主子进程间文件描述符(File Descriptor)的继承与共享。当主进程创建子进程后,子进程会默认继承父进程所有打开的文件描述符。这种机制虽然在某些场景下非常有用(比如管道通信),但也可能引发一些意想不到的问题。
最典型的场景就是:主进程打开了一个文件(比如日志文件),然后fork出子进程。此时如果主进程和子进程同时对这个文件进行写入操作,就可能出现写入内容交错、文件指针混乱等问题。更糟糕的是,即使主进程关闭了这个文件描述符,子进程仍然持有该文件的引用,可能导致文件无法真正被操作系统释放。
注意:这个问题在守护进程(daemon)编程中尤为常见,因为守护进程通常需要fork子进程并在后台运行。
2. 文件描述符继承机制深度解析
2.1 fork()系统调用的行为特点
当程序调用fork()创建子进程时,子进程会获得父进程地址空间的副本,包括:
- 进程地址空间
- 进程上下文
- 进程堆栈
- 打开的文件描述符表
关键点在于:子进程获得的文件描述符实际上指向的是和父进程相同的文件表项(file table entry)。这意味着:
- 文件偏移量是共享的:如果父进程和子进程同时写入文件,它们的写入位置会相互影响
- 文件状态标志是共享的:比如O_APPEND标志
- 关闭行为有特殊规则:只有当所有进程都关闭了文件描述符,文件才会真正关闭
2.2 实际案例演示
考虑以下代码片段:
c复制#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("test.log", O_WRONLY | O_CREAT, 0644);
if (fd < 0) {
perror("open failed");
return 1;
}
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
}
if (pid == 0) { // 子进程
write(fd, "child writing\n", 14);
close(fd);
} else { // 父进程
write(fd, "parent writing\n", 15);
close(fd);
}
return 0;
}
运行这个程序后,查看test.log文件,你可能会看到类似这样的内容:
code复制parent writing
child writing
或者:
code复制c
