操作系统(Linux)---fork()函数解析与进程

          我在一个二本类院校,对于linux是不在课程范围的,属于个人爱好吧。自己学习了一些,操作系统上学期开了,老师很水,就期末最后两天学了整本书。

上学期去图书馆借了一本《linux内核设计与实现》当时看的是第二版,但是没有看完,其实收获还是挺多的。

         说回正题,前两天有个好朋友给我讲他们操作系统还有实验课,当时我就懵了,确实差距大。于是让他把实验课题发给我,我想看看。很多只有做过实验,才能深刻体会。


编制一段程序,使用系统调用fork( )创建两个子进程,这样在此程序运行时,在系统中就有一个父进程和两个进程在活动。每一个进程在屏幕上显示一个字符,其中父进程显示字符A进程分别显示字符 B和字符C。试观察、记录并分析屏幕上进程调度的情况。

设计思路:

进程的出生:

随着一句fork,一个新进程呱呱落地,但它这时只是老进程的一个克隆

调用fork创建的子进程,将共享父进程的代码空间,复制父进程数据空间,此时子进程会获得父进程的所有变量的一份拷贝。

系统调用 fork(),在fork函数调用完,如果创建成功就会返回两个进程,一个是father进程和一个新的进程son1(fork的一次调用两次返回)。

fork()返回值意义如下:

=0:在子进程中,表示当前进程是子进程。

>0:在父进程中,返回值为子进程的id值(唯一标识号)。

-1:创建失败。

再在返回的father进程里面调用fork()创建一个新的进程son2.

然后分别在进程打印A,B,C,然后实验一下。


看看运行结果:


What happend ?
这时我们就有必要了解一下进程的死亡。

死亡:
人有生老病死,进程也一样:
自然死亡 ,即运行到main函数的 最后一个 } ,从容地离我们而去。
自杀 (自杀有2种方式)
调用 exit函数
在main函数内使用 return
无论哪一种方式,它都可以留下遗书,放在返回值里保留下来。
被谋杀 ,被其它进程通过另外一些方式结束他的生命。
三个进程都是在return后死亡,而他们的死亡顺序是又操作系统控制。
我们再来来看看实验结果:

发现father的pid是10982,son1的father的pid是10982,然后到了son2这里就会发现father的id是1965。
这又发生了什么?看看下面两个特别的进程。

孤儿进程

     父进程在子进程结束之前死亡(return或者exit)

     在一定时间内,当系统发现孤儿进程时,init进程就收养孤儿进程,成为它的father,child进程exit后资源回收都又init进程完成。

僵死进程

     子进程在父进程之前结束了,但是父进程没有用wait或waitpid回收子进程


三个进程都是在return后死亡,而他们的死亡顺序是又操作系统控制。所以父进程在son2结束 死亡。son2成了孤儿进程,被init进程回收。在大多数linux上面init进程的pid为1,但是据说ubuntu为了减轻pid为1进程的压力,将其内核修改过。我init进程的pid为1965。

wait挂起调用它的进程,直到它的任一子进程退出或者收到一个不能忽略的信号为止,如果在父进程执行wait调用前就已经有子进程退出,则立刻返回。函数成功调用后返回结束运行子进程的ID,否则返回-1。进程死掉以后,会留下一具僵尸(一些数据),wait充当了殓尸工,把僵尸推去火化,使其最终归于无形。

因此我们修改下代码, 在父进程里面调用wait()函数收回子进程资源避免僵死进程。



看看运行结果:



好了,终于结束了。

如果父进程忘记wait(),就会存在僵死进程。过多的僵死进程就会占用系统资源,影响性能,为了预防这种情况,便有两次调用fork()。

Why调用两次fork()

为了避免child成为僵死进程,可以人为的杀死父进程,可以让其成为孤儿进程被init接管,但是如果父进程是服务器进程或者一些重要的进程不能立刻被杀死的话,僵死进程就一直存在,影响系统性能,所以为了避免这种情况,要调用两次fork(),让孙子线程成为工作线程,如果它成为僵死线程的话,可以杀死它的父进程。

在实验中,我还设置了一个数count,是为了实验fork创建的子进程,每个进程是父进程的一份复制,但是又有自己的空间,每个进程都有自己的变量。

son1创建时,会复制变量count=0,但是count是存在son1的数据段中,所以son1的count和father以及son2拥有不同的地址,每个count是独立的,不是共用的。


(但是这个程序存在一个问题,就是有两个子进程但是只wait( )了一次·,如果有一个子进程返回,wait调用就结束。父进程可能不会回收第二个返回的子进程,所以还是存在僵死进程的问题。可以使用waitpid()函数来指定回收子进程)

#include <stdlib.h>
#include <stdio.h>
int main()
{
int count=1;
int pid1;
printf("Recently pid is %d \n",getpid());
pid1 = fork();
if(pid1<0)
{
printf("error in fork!");
}
else if(pid1==0)
{
count++;
printf("My father'pid is %d I'm son1,my pid is %d   B\n",getppid(),getpid());
}
else
{
int pid2=fork();
if(pid2<0)
{
printf("error in fork!");
}
if(pid2==0)
{
count++;
printf("My father'pid is %d I'm son2,my pid is %d  C \n",getppid(),getpid());
}
else
{
waitpid(pid1,NULL);
waitpid(pid2,NULL);
count++;
printf("I'm father,my pid is %d,   A \n",getpid());
}
}
printf("%d \n",count);
return 0;
}


(但是如果子进程多了的话应该怎样回收子进程,避免僵死进程呢?写循环吗,循环可能不是一个好的解决办法。很多人说可以用信号实现,有时间研究一下吧,也希望知道的同学给我留言,在此感激不尽,阿里嘎多)

最后,fork与vfork的区别
1. fork要拷贝父进程的数据段;而vfork则不需要完全拷贝父进程的数据段,在子进程没有调用exec和exit之前,子进程与父进程共享数据段


2. fork不对父子进程的执行次序进行任何限制;而在vfork调用中,子进程先运行,父进程挂起,直到子进程调用了exec或exit之后,父子进程的执行次序才不再有限制

                        

                                                                                



猜你喜欢

转载自blog.csdn.net/qq_23948283/article/details/52627864
今日推荐