目录
一.僵死进程与孤儿进程
在说明僵死进程与孤儿进程之前 我们先得明白进程是什么
1.进程概念
进程: 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。
通俗点讲,进程是一段程序的执行过程,是个动态概念。
2.进程状态
进程一般有三种状态:运行,就绪,阻塞。
程序运行必须加载在内存中,当有过多的就绪态或阻塞态进程在内存中没有运行,因为内存很小,有可能不足。系统需要把他们移动到内存外磁盘中,称为挂起状态。就绪状态的进程挂起就是挂起就绪状态,阻塞进程挂起就称为阻塞挂起状态。
每个进程的产生都有自己的唯一的ID号(pid),并且附带有一个它父进程的ID号(ppid)。进程死亡时,ID被回收。
3.僵死进程
3.1什么是僵死进程?
僵死进程:子进程先于父进程结束,父进程没有获取子进程退出码,子进程变为僵死进程。
产生僵尸进程的代码如下:
1. #include <stdio.h>
2. #include <stdlib.h>
3. #include <unistd.h>
4. #include <string.h>
5. #include <assert.h>
6.
7. int main(int argc, char* argv[],char* envp[])
8. {
9. char * s = NULL;
10. int n = 0;
11.
12. pid_t pid = fork();
13. assert( pid != -1 );
14.
15. if ( pid == 0 )
16. {
17. s = "child";
18. n = 4;
19. }
20. else
21. {
22. s = "parent";
23. n = 10;
24. }
25.
26. int i = 0;
27.
28. for(; i < n; i++ )
29. {
30. printf("pid=%d,s=%s\n",getpid(),s);
31. sleep(1);
32. }
33.
34. exit(0);
35. }
运行结果:
当子进程结束后,并没有消失,仍然可以在系统中观测到,此时子进程其实已经运行结束了,这个时候子进程的状态被称为僵死状态,系统把处于该类状态的进程称为僵死进程。
当产生僵死进程后我们该如何处理呢?
3.2如何处理僵死进程
一般情况下我们都是:父进程通过调用 wait()完成。
还是以刚才的代码为例,我们调用 wait()来处理僵死进程:
1. #include <stdio.h>
2. #include <stdlib.h>
3. #include <unistd.h>
4. #include <string.h>
5. #include <assert.h>
6. #include <sys/wait.h>
7.
8. int main()
9. {
10. char * s = NULL;
11. int n = 0;
12.
13. pid_t pid = fork();
14. assert( pid != -1 );
15.
16. if ( pid == 0 )
17. {
18. n = 4;
19. s = "child";
20. }
21. else
22. {
23. n = 10;
24. s = "parent";
25.
26. int val = 0;
27. int id = wait(&val);
28.
29. if ( WIFEXITED(val) )
30. {
31. printf("id=%d,val=%d\n",id,WEXITSTATUS(val));
32. }
33.
34. }
35.
36. int i = 0;
37. for( ; i < n; i++ )
38. {
39. printf("pid=%d,s=%s\n",getpid(),s);
40. sleep(1);
41. }
42. exit(3);
43. }
运行结果:
当子进程结束后,彻底从系统中消失了,并没有变成僵死进程,这里我们的僵死进程就得到了解决。
4.孤儿进程
4.1什么是孤儿进程?
孤儿进程:当一个父进程执行完成退出后,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。
4.2如何处理孤儿进程
孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/types.h>
int main()
{
pid_t pid=fork();
if(pid==0)
{
printf("child ppid is %d\n",getppid());
sleep(10); //为了让父进程先结束
printf("child ppid is %d\n",getppid());
}
else
{
printf("parent id is %d\n",getpid());
}
exit(0);
}
从执行结果来看,此时由pid == 4168父进程创建的子进程,其输出的父进程pid == 1,说明当其为孤儿进程时被init进程回收,最终并不会占用资源,这就是为什么要将孤儿进程分配给init进程。
5.孤儿进程与僵死进程的区别
孤儿进程与僵死进程不同的是,由于父进程已经死亡,系统会帮助父进程回收处理孤儿进程。所以孤儿进程实际上是不占用资源的,因为它终究是被系统回收了。不会像僵死进程那样占用ID,损害运行系统。
二.内存地址问题
对于内存地址问题,最容易使我们混淆的就是物理地址与逻辑地址,下面我们就对内存地址进行说明。
先来一段代码:
#include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <string.h>
# include <assert.h>
# include <sys/wait.h>
int main(){
int n=0;
char *s =NULL;
pid_t pid=fork();
if(pid==-1){
exit(1);
}
if(pid==0){
s="child";
n=7;
}else{
s="parent";
n=3;
for(i=0; i < n; i++ )
{
printf("s=%s,cur_pid=%d,cur_ppid=%d",&n=%p,n=&d,s,getpid(),getppid(),&n,n);
}
printf("\n");
exit(0);
}
运行结果:
当我们看到这个结果时我们就会有疑惑这里为什么是不同的父子进程但是地址是相同的?
我们可能下意识觉得 &地址的值都是相同的,但是如果这样的话 不就自相矛盾了吗 一个地址存储了这么多的数据 ,编译的时候不会出错吗?
在这里因为子进程在赋值的时候–是赋值了所有的父进程有关的内容,到新的一块儿内存上,所以物理地址是不相同的。
这里面显示出来的是逻辑地址 而不是物理地址 。
在上图中可以看到逻辑地址相同均为855 但是物理地址是不相同的。
那为什么逻辑地址是相同的?
操作系统在分内存的时候,分成许多块,在使用的过程中我们可以理解为它需要一个管家来管理和分配。这个时候分配的过程就导致了他的逻辑地址相同。
下面拿这个图举例就特别容易理解:
橙色:父进程 绿色:子进程
进行fork后 逻辑地址均是0 1 2 3 但是不同进程对应的物理地址是不同的。
所以我们需要记住程序中看到的地址为逻辑地址。
所以:我们无法直接使用物理地址,需要通过逻辑地址逆推找到物理地址。