在 fork()
调用之后,父进程和子进程会共享相同的代码和数据段,但是它们会有各自独立的虚拟空间,尤其是在数据段的部分。以下是一些关于父子进程虚拟空间的重要说明:
-
代码和只读数据:
- 父子进程共享相同的代码和只读数据。这些部分在内存中是相同的,而不会被修改。
-
数据段(Data Segment):
- 父子进程有各自独立的数据段。任何一个进程对其数据段的修改都不会影响另一个进程的数据段。
-
堆(Heap):
- 堆是动态分配内存的区域,通过
malloc
、calloc
等函数进行分配。在fork()
之后,父子进程都有各自独立的堆。在一个进程中释放堆中的内存不会影响另一个进程。
- 堆是动态分配内存的区域,通过
-
栈(Stack):
- 栈是用于函数调用和局部变量存储的区域。在
fork()
之后,父子进程有各自独立的栈。栈上的变化不会影响另一个进程。
- 栈是用于函数调用和局部变量存储的区域。在
-
文件描述符表:
- 父子进程共享相同的文件描述符表。当父子进程中一个进程关闭了一个文件描述符时,不会影响另一个进程。
-
内存映射和共享内存:
- 在
fork()
之后,任何通过mmap
创建的内存映射都会在父子进程之间共享,这可能会导致需要额外的同步。
- 在
总体来说,虽然父子进程共享一些内存区域,但是它们各自有独立的虚拟地址空间,使得彼此的操作不会相互干扰。这是通过使用虚拟内存机制来实现的,每个进程都有自己的虚拟地址空间,这个虚拟地址空间与物理内存的映射由操作系统负责管理。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
//为什么进程中的num地址是一样的呢?
int num = 10;
printf("original num: %d\n", num);
//输出原地址
printf("Address of original num: %p\n", &num);
pid_t pid = fork();
if (pid > 0){
printf("Parent process, PID = %d\n", getpid());
num += 1;
printf("num+1 in parent process: %d\n", num);
//输出父进程中num的地址
printf("Address of num in parent precess: %p\n", &num);
}
if (pid == 0){
printf("Child process, PID = %d\n", getpid());
num += 2;
printf("num+2 in child process: %d\n", num);
//输出子进程中num的地址
printf("Address of num in child precess: %p\n", &num);
}
return 0;
}
父子进程内核中的pid是不一样的。
"读时共享,写时拷贝" 是指在 fork()
时,父子进程共享相同的内存页,但是只有在其中一个进程尝试写入该页时,才会发生拷贝操作。这个机制是为了提高效率,避免不必要的内存复制。
具体来说:
-
读时共享(Read-Shared):
- 在
fork()
时,父子进程共享相同的物理内存页。 - 如果父进程或子进程中的任意一个进程尝试读取这个共享页,那么它们都会看到相同的内容。
- 这样的共享使得父子进程在开始时能够共享相同的状态。
- 在
-
写时拷贝(Copy-On-Write):
- 如果父子进程中的任何一个尝试对共享的内存页进行写操作,操作系统就会触发一个拷贝操作。
- 拷贝操作会创建一个新的物理内存页,然后将写入的数据复制到新页上,使得修改只影响修改的那个进程,而不影响其他进程。
- 这样可以确保父子进程之间的写操作互不影响,每个进程都有自己的一份私有拷贝。
这种机制的优势在于在 fork()
之后,如果父子进程中的一个进程只是读取共享的数据而不修改,那么它们可以共享相同的物理内存,而无需进行实际的拷贝操作,从而提高了效率。只有在有写操作时才会发生实际的拷贝,确保了数据的一致性和独立性。