fork创建进程
fork系统调用是用于创建进程的,由fork创建的新进程被称为子进程。
父进程和子进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程数据 空间、堆和栈的副本。注意,这是子进程所拥有的副本。父进程和子进程并不共享这些存储空间部分。父进程和子进程共享正文段。
- fork系统调用无参数。pid_t fork(void)
- fork被调用一次,但返回两次。两次返回的区别在于子进程的返回值是0,父进程的返回值是子进程的ID。
- 将子进程ID返回给父进程的理由是:因为一个进程的子进程可以有多个,并且没有一个函数可以获得其所有子进程的进程ID。将0返回给父进程的理由是:因为一个子进程的父进程只有一个,并且可以通过getppid()函数来获得父进程ID(进程ID 0 总是由内核交换进程使用,所以一个子进程的进程ID不可能为0.)
测试代码
#include <cstring>
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid;
int i=0;
pid = fork();
if(pid == 0){
while(1){
i = i-1;
printf("%d, son process\n",i);
}
}
else if(pid>0){
while(1){
i = i+1;
printf("%d, father process\n",i);
}
}
else{printf("failed in creating son process");}
return 0;
}
执行命令:
gcc fork_demo.cpp fork_demo -g -lpthread
测试结果:
从这里可以分析结果得出
- 父进程和子进程开始的变量的值是相同的,我理解的是子进程开辟了自己的一块资源空间,然后把父进程的变量copy过去,所以后面++和–都是针对各自进程中的值。
- 我认为就是父进程和子进程其实就是两个并行的进程(这里指的并行是想说明每个进程都是独立的)
在《APUE》一书中说道:
一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的,这取决于内核所使用的调度算法。如果要求父进程和子进程之间相互同步,则要求某种形式的进程间通信。
由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段、栈和堆的完全副本,作为替代,使用了”写时复制“(Copy-on-write,COW)技术。这些区域由父进程和子进程共享,并且内核将他们的访问权限改为只读。如果父进程和子进程中的任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常时虚拟存储系统中的一”页“。
书上的说法和我的猜想有一些不太一致,因此我在修改i值之前对i进行了取地址运算,再对比一下修改之后的地址,看下书上说的是否正确。
验证程序:
#include <cstring>
#include <stdio.h>
#include <unistd.h>
//using namespace std;
int main(){
pid_t pid;
int i=0,j;
printf("[before] %p\n",&i);
pid = fork();
if(pid == 0){
for(j=0;j<5;j++){
i = i-1;
printf("[child] %p\n",&i);
printf("%d, son process\n",i);
}
}
else if(pid>0){
for(j=0;j<5;j++){
i = i+1;
printf("[father] %p\n",&i);
printf("%d, father process\n",i);
}
}
else{printf("failed in creating son process");}
return 0;
}
before是修改之前的地址,father是父进程修改后的i的地址,child是子进程修改后的i的地址。
看了一位博主的博客上面说是物理空间在写操作之后发生了变化。应该是逻辑空间没变。。