复制进程映像:使用fork函数得到的子进程从父进程继承了整个进程的地址空间,包括:进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程
优先级、进程组号当前工作目录、根目录、资源限制、控制终端等。
子进程与父进程的区别在于:
1、父进程设置的锁,子进程不继承(例如对于一个排他锁,父进程设置了锁,子进程再设置,显然不合适)
2、各自进程ID和父进程ID不同
3、子进程的未决警告被清除
4、子进程的未决信号集设置为空集
#include <unistd.h>
#include <sys/types.h>
pid_t fork(void);//无参数,失败返回-1。两次返回是在各自的地址空间中返回的。
A 进程fork进入内核
孤儿进程:父进程先于子进程退出,托孤给init进程
僵尸进程:子进程先退出,父进程还未查询子进程的退出状态,子进程就处于僵死状态(defunct)。
为何父进程返回值大于0(新进程ID号码),子进程返回0:因为子进程PCB中保存有进程ID和父进程ID,所有即便返回0,子进程也能获取这两个值;但父进程若返回0,则它
无法得知新创建的子进程的ID。
fork一次调用两次返回(是在各自的进程地址空间中返回的),fork成功,意味着创建了一个进程副本,两个进程。fork陷入内核,拷贝父进程代码,所以子进程也有fork,并返回。
1 #include<unistd.h> 2 #include<sys/types.h> 3 #include<stdlib.h> 4 #include<stdio.h> 5 #include<errno.h> 6 #include<string.h> 7 #include<signal.h> 8 #define ERR_EXIT(m) 9 do 10 { 11 perror(m); 12 exit(EXIT_FAILURE); 13 }while(0) //宏要求一条语句 14 15 int main() 16 { 17 signal(SIGCHLD,SIG_IGN);//避免僵死进程 18 printf("before fork pid=%d ",getpid()); 19 pid_t pid; 20 pid=fork();//进程代码段、数据段、堆栈段等一样,新进程的堆栈段和PCB也跟父进程一样,所以下一条指令地址也一样,所以不会从开头运行。所以进程分支在fork之后。 21 if(pid==-1) 22 ERR_EXIT("fork error"); 23 if(pid>0) 24 { 25 printf("this is parent pid=%d, child pid=%d ",getpid(),pid); 26 } 27 28 else if(pid==0) 29 { 30 //sleep(5);//父进程先结束 31 printf("this is child pid=%d, parent pid=%d ",getpid(),getppid()); 32 } 33 34 return 0; 35 } 36 /* 37 子进程先结束 38 before fork pid=48714 39 this is parent pid=48714, child pid=48715 40 this is child pid=48715, parent pid=48714 41 */
fork函数陷阱:1-->2-->4-->8 (核心:fork开始,会有两个进程执行相同代码段)
#include<unistd.h> #include<sys/types.h> #include<stdlib.h> #include<stdio.h> #include<errno.h> #define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); }while(0) //宏要求一条语句 int main() { fork(); fork(); fork(); printf("ok "); return 0; /*8 ok ok ok ok ok ok ok ok */ }
写时复制(copy on write):
如果多个进程要读取(仅仅读取)它们自己的那部分资源的副本,那么复制是不必要的(比如代码段),每个进程只要保存一个指向这个资源的指针就可以了。如果一个进程要修改自己那份资源的副本,那么就会复制那份资源。这就是写时复制。资源需要修改才要复制。其他进程仍然共享。实际上不会被修改的数据是共享的。