一、一个进程的构成
一个进程由正文段(即代码段)、用户数据段以及系统数据段。
其中系统数据段又称进程控制块(PCB),是给操作系统进行调度用的。系统数据段中存放了关于这个进程的:PID 、PPID、优先级、占用的资源、该进程的状态等等。
fork函数用于进程创建。
看下面一段代码
1: #include <stdio.h>
2: #include <unistd.h>
3: #include <stdlib.h>
4: #include <sys/types.h>
5:
6:
7: int main(void)
8: {
9: int a = 0;
10: pid_t pid;
11:
12:
13: if((pid = fork()) == -1)
14: {
15: printf("fork error!\n");
16: exit(-1);
17: }
18:
19: if(pid == 0)
20: {
21: a++;
22: printf("child result= %d, pid=%d, ppid=%d, a=%d, &a=%p\n",pid,getpid(),getppid(),a,&a);
23: }
24: else
25: {
26: printf("parent result= %d, pid=%d, ppid=%d, a=%d, &a=%p\n",pid,getpid(),getppid(),a,&a);
27:
28: }
29: return 0;
30: }
比如此刻有一个进程P1刚开始执行该程序,但执行到fork函数,进入fork函数体内,此时程序计数器PC(PC中存放的是返回地址,当返回后会把返回值给了pid这个变量)压栈, fork会创建一个P1的子进程P2,P2同样也是有三部分构成(正文段(即代码段)、用户数据段以及系统数据段),这三段基本上与其父进程完全一样,包括压到栈的PC,不同的是他们的pid以及ppid。P2的ppid是P1的进程号,而P1的ppid就是shell的进程号。可以这么理解,此时在内存中有如下两个同样的部分。
父PS1的 子PS2的
执行 pstree 命令后
他们的家族关系是:
可以看到,P1的父进程是bash。
所以当执行完fork函数后,P1和P2都返回,PC出栈,但是P2并不再执行fork函数和fork函数前面的程序,而是将fork的返回值赋值给变量pid。
看一下输出:
二、注意
- 当子进程先于父进程结束,子进程的状态变为 Z,又称“僵尸态”,即虽然结束了,但是还占有一些资源。子进程的资源回收由其父进程负责。
- 当父进程先于子进程结束,子进程有init进程负责回收。但是此时子进程不在受shell控制,变为后台进程,假如子进程中有getchar等需要用户输入,getchar会返回-1。
当出现第二种情况时,gnome-terminal输出会有点乱:
- 从输出可以看出,两次输出的a的地址相同,原因:
即: 子进程和父进程中的a的虚拟地址相同,但是物理地址不同。其中内存管理单元MMU完成虚拟内存和物理内存的转换。