继承的资源
- 进程地址空间:子进程获得与父进程相同的用户空间地址布局,包括代码段、数据段、堆和栈等。不过这些内存区域是写时复制(Copy - On - Write)的,即只有当父子进程中有一方尝试修改内存时,才会真正复制内存页。
- 文件描述符:子进程继承父进程打开的文件描述符,这些文件描述符指向相同的文件表项,意味着父子进程共享文件的偏移量等状态。
- 信号处理函数:子进程继承父进程设置的信号处理函数,对相同信号的处理方式与父进程相同。
- 当前工作目录:子进程继承父进程的当前工作目录。
- 用户ID和组ID:子进程继承父进程的实际用户ID、有效用户ID、实际组ID和有效组ID。
新分配的资源
- 进程ID(PID):每个进程都有唯一的PID,子进程会获得一个新的、唯一的PID。
- 父进程ID(PPID):子进程的PPID是父进程的PID,与父进程的PPID不同。
- 内存资源:虽然子进程继承了地址空间布局,但实际的物理内存是新分配的(采用写时复制机制,一开始物理内存共享,修改时才分配新的物理内存)。
- 文件描述符表:虽然文件描述符指向相同文件表项,但子进程有自己独立的文件描述符表。
实际应用中的意义
- 简化编程模型:对于一些需要进行并发操作且共享部分状态的场景,如网络服务器中,子进程可以继承父进程打开的监听套接字文件描述符,这样子进程可以直接处理客户端连接,无需重新初始化网络连接相关资源,简化了编程逻辑。
- 快速启动:通过继承父进程的一些资源,子进程无需重新构建整个运行环境,能快速启动并开始执行任务,提高了程序的响应速度。
可能带来的问题
- 资源竞争:父子进程共享文件描述符可能导致资源竞争问题。例如,父子进程同时对共享文件进行写操作,可能导致文件内容混乱。
- 信号处理冲突:继承相同的信号处理函数可能引发问题。如果父进程设置了一个信号处理函数用于清理资源,子进程继承后,在不恰当的时候收到信号,可能会执行不适合子进程的清理操作。
解决问题的示例
- 解决资源竞争问题:可以使用文件锁机制。例如,在父子进程写文件前,通过
fcntl
函数获取文件锁。
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
int main() {
int fd = open("test.txt", O_CREAT | O_WRONLY, 0666);
if (fd == -1) {
perror("open");
return 1;
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
close(fd);
return 1;
} else if (pid == 0) { // 子进程
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = 0;
if (fcntl(fd, F_SETLKW, &lock) == -1) {
perror("fcntl");
close(fd);
return 1;
}
write(fd, "Child process writing\n", 20);
lock.l_type = F_UNLCK;
if (fcntl(fd, F_SETLK, &lock) == -1) {
perror("fcntl");
close(fd);
return 1;
}
close(fd);
} else { // 父进程
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = 0;
if (fcntl(fd, F_SETLKW, &lock) == -1) {
perror("fcntl");
close(fd);
return 1;
}
write(fd, "Parent process writing\n", 20);
lock.l_type = F_UNLCK;
if (fcntl(fd, F_SETLK, &lock) == -1) {
perror("fcntl");
close(fd);
return 1;
}
close(fd);
}
return 0;
}
- 解决信号处理冲突问题:子进程可以在创建后重新设置适合自己的信号处理函数。
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void parent_signal_handler(int signum) {
printf("Parent received signal %d\n", signum);
}
void child_signal_handler(int signum) {
printf("Child received signal %d\n", signum);
}
int main() {
signal(SIGINT, parent_signal_handler);
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
} else if (pid == 0) { // 子进程
signal(SIGINT, child_signal_handler);
while (1) {
sleep(1);
}
} else { // 父进程
while (1) {
sleep(1);
}
}
return 0;
}