fork函数:从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程,fork之后通常要用if分流,返回值为0,则为子进程,返回值为大于0(暨为创建出子进程的pid),则为父进程,返回值小于0则创建子进程失败,父子进程的pcb中有相同的代码段,因此通过分流来让父子进程执行不同代码。
Q1:子进程到底是从fork这行代码开始运行,还是从fork之后的代码开始运行?
A1: 因为父进程在执行完毕fork指令之后,程序计数器(pcb中)当中保存的是fork之后的指令,子进程拷贝父进程的pcb,因此也将程序计数器拷贝过去,所以从fork之后的执行开始运行。
Q2:子进程先运行or父进程先运行?
A2: 抢占式运行(cpu少,进程数量多造成的并发情况)。
提示:以下是本篇文章正文内容
僵尸状态:当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵尸进程,僵尸进程会已终止状态“Z”保留在进程表中,并且会一直在等待父进程读取退出状态代码。
僵尸进程:当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。
模拟场景:父进程死循环,子进程退出。
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
6 int i = 10;
7 pid_t ret = fork();
8 if(ret < 0)
9 {
10 perror("fork");
11 return -1;
12 }
13 else if(ret == 0)
14 {
15 i+=10;
16 printf("我是子进程,pid是:%d,i = %d\n",getpid(),i);
17 printf("我的父进程pid是:%d\n",getppid());
18 }
19 else
20 {
21 while(1)
22 {
23 printf("我是父进程,pid是%d, i = %d\n", getpid(),i);
24 printf("父进程的父进程pid是%d\n",getppid());
25 sleep(1);
26 }
27 }
28 return 0;
29 }
getpid():获取进程的pid;
getppid():获取父进程的pid。
ps aux:查看进程命令
ps -ef:查看父子关系
kill 【pid】:杀死该进程
kill -9 【pid】: 强杀这个进程
在执行之后:
可以看出子进程先于父进程退出,父进程并未回收其资源,说明在操作系统内核中子进程的pcb依然存在而并未被释放,子进程变为僵尸进程而且无法被kill命令杀掉(因为无法杀掉一个已经死去的进程)。
僵尸进程的危害:
占用操作系统的资源,内存泄漏。
如何解决僵尸进程问题?
1.重启操作系统(不推荐);
2.杀掉父进程。why?杀掉父进程后子进程变为孤儿进程然后被1号init进程领养,最后被1号进程回收(不推荐);
3.进程等待。
父进程等待子进程退出,获取退出子进程返回值,释放子进程资源,避免产生僵尸进程。
pid_t wait(int * status)
等待任意一个子进程退出,通过status获取退出返回值,释放资源。(默认为一个阻塞等待接口——为了完成一个功能发起的调用接口,若功能完成条件不具备(例如子进程没退出),一直等待)
参数:
status(传址操作)用来获取子进程退出状态,不关心可设置为NULL。
返回值:成功则返回退出子进程的pid;错误(例如没有子进程)则返回-1。
pid_t waitpid(pid_t pid, int * status, int options)
可以等待任意一个子进程退出,也可以等待指定的子进程退出。
参数
pid若为-1,则为等待任意子进程,若大于0,则等待指定子进程;
status(传址操作)用来获取子进程退出状态,不关心可设置为NULL,内部有两个分支:1.WIFEXITED(status)获取异常退出信号;
2.WEXITSTATUS(status)获取退出状态信息;
options:默认为0为阻塞等待;若设置为WNOHANG则为非阻塞等待——(若完成条件不具备,立即报错返回)
**返回值:**大于0表示退出子进程的pid;等于0表示無子进程退出;错误返回-1.
来个例子说明一下:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/wait.h>
5
6 int main()
7 {
8 pid_t pid = fork();
9 if(pid<0)
10 {
11 perror("fork");
12 return -1;
13 }
14 else if(pid == 0)
15 {
16 printf("子进程将在5秒后退出\n");
17 sleep(5);
18 exit(99);
19 }
20 while(1)
21 {
22 printf("父进程还活着\n");
23 sleep(1);
24 }
25 }
我们先不用wait接口,执行五秒后子进程退出成为僵尸进程。
然后我们加上wait(NULL),此时为阻塞等待子进程退出。
然后我们将wait接口换位waitpid(-1,NULL,0),此时完全等价于wait(NULL)。
设为非阻塞之后(保持其他代码不变)
然后对waitpid接口做循环处理
然后我们再来看一下status里面接收的退出状态信息,因为我们代码里给的子进程退出为eixt(99),
5
6 int main()
7 {
8 pid_t pid = fork();
9 if(pid<0)
10 {
11 perror("fork");
12 return -1;
13 }
14 else if(pid == 0)
15 {
16 printf("子进程将在5秒后退出\n");
17 sleep(5);
18 exit(99);
19 }
20 //wait(NULL);
21 int ret, status;
22 while((ret= waitpid(-1,&status,WNOHANG)) == 0)
23 {
24 printf("现在没有子进程退出,再等一秒试试\n");
25 sleep(1);
26 }
27 if(ret > 0)
28 {
29 printf("退出子进程的pid为:%d ; 退出状态信息为:%d\n", ret, status);
30 }
31 while(1)
32 {
33 printf("父进程还活着\n");
34 sleep(1);
35 }
36 }
通过上图我们可以得出status这个整型中,高十六位不使用,低十六位中的高八位(一个字节)保存的是退出状态的信息。
而实际中低八位保存了两个东西,第七位保存了core dump标志,第0-6位保存了终止信号(例如11号信号为段错误),如下图。
若想查看异常返回信号值:status & 0x7f;对应上文的WIFEXITED(status)
若想查看退出状态信息:status >> 8; status & 0xff;对应上文的WEXITSTATUS(status)
注:上面这两个接口,如果在调用之前已经有子进程退出,则直接进行处理并返回退出信息,不再等待。
僵尸进程会造成资源泄漏,处理僵尸进程可以退出父进程,最好的避免僵尸进程的方法是:进程等待。
番外:
孤儿进程
产生:父进程先于子进程退出,子进程成为孤儿进程。
特性:运行在后台,父进程成为1号进程。
守护进程:特殊的孤儿进程,产生孤儿进程后设置新的会话(setsid),运行在后台,脱离与终端的关系,脱离登录会话。