前言
进程是操作系统最重要的一个概念。对大多数操作系统内的进程能并发执行,他们可以动态创建和删除,因此操作系统必须提供某种机制以创建和终止进程。
介绍
进程创建
进程在其执行过程中,能通过创建进程系统调用创建多个新进程,创建进程称为父进程,而新进程称为子进程。每个新进程可以再创建其他进程,从而形成进程树。 大多数操作系统根据唯一的进程标识符来识别进程,pid通常是一个整数值。
通常进程需要一定的资源来完成任务。在一个进程创建子进程时,子进程可能从操作系统那里直接获得资源,也可能从其父进程那里获得资源。父进程可能必须在其子进程之间分配资源或共享资源。限制子进程只能使用父进程的资源能防止创建过多的进程带来的系统超载。 在进程创建时,除了得到各种物理和逻辑资源外,初始化数据由父进程传递给子进程。 当进程创建新进程时,有两种执行可能:
- 父进程与子进程并发执行。
- 父进程等待,直到某个或全部子进程执行完。
新进程的地址空间也有两种可能:
- 子进程是父进程的复制品(具有与父进程相同的程序和数据)
- 子进程装入另一个新程序
UNIX系统通过系统调用可创建新进程。新进程通过复制原来进程的地址空间而成。这种机制允许父进程与子进程方便的进程通信。两个进程都继续执行位于系统调用fork()之后的指令。但是有一点不同:对于新进程,系统调用fork()的返回值为;而对于父进程,返回值为子进程的进程标识符(非零)。 通常,在系统调用fork()之后,一个进程会使用系统调用exec(),已用新程序来取代进程的内存空间(也就是子进程运行的是和父进程不同的程序,执行不同的功能)。系统调用exec()将二进制文件装入内存(消除了原来包含系统调用exec()的程序的内存映射),并开始执行。采用这种方式,两个进程能互相通信,并能按各自的方法执行。父进程能创建更多的子进程,或者如果在子进程运行时没有什么可做,那么它采用系统调用wait()把自己移出就绪队列来等待子进程的终止。 父进程通过系统调用wait()来等待子进程的完成。当子进程完成时(通过显示或隐式调用exit()),父进程会从wait()调用处开始继续,并调用系统调用exit()以表示结束。 windows中,进程的创建是通过Createprocess()函数。然而fork()中子进程继承了父进程的地址空间,而Createprocess()生成函数时,需要将一个特殊程序装入子进程的地址空间。进一步讲,与fork()不需要传递参数不同,Createprocess()至少需要传递十个参数。
下面是一个典型的Unix系统父进程创建子进程的例子:
在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)… 为什么两个进程的fpid不同呢,这与fork函数的特性有关。 fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次 它可能有三种不同的返回值:
- 在父进程中,fork返回新创建子进程的进程ID;
- 在子进程中,fork返回0;
- 如果出现错误,fork返回一个负值;
这两个进程的变量都是独立的,存在不同的地址中,不是共用的,这点要注意。可以说,我们就是通过pid来识别和操作父子进程的。还有人可能疑惑为什么不是从#include处开始复制代码的,这是因为fork是把进程当前的情况拷贝一份,执行fork时,进程已经执行完了int count=0;fork只拷贝下一个要执行的代码到新的进程。
进程终止
当进程完成执行最后的语句并使用系统调用exit()请求操作系统删除自身时,进程终止。这时,进程可以返回状态值到父进程。所有进程资源会被操作系统释放。 进程通过适当的系统调用能终止另一个进程。通常,只有被终止进程的父进程才能执行这一系统调用。当一个进程创建新进程时,新创建进程的标识符要传递给父进程。 有些系统,包括VMS,不允许子进程在父进程已终止的情况下存在。对于这类系统,如果一个进程终止,那么它的所有子进程也将终止。这种现象,称之为级联终止,通常由操作系统进行。 Unix下,可以通过系统调用exit()来终止进程,父进程可以通过系统调用wait()以等待子进程的终止。如果父进程终止,那么其所有子进程会以init进程作为父进程。因此,子进程仍然有一个父进程来收集状态和执行统计。