一、进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。进程可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。进程不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。通俗点讲,进程是一段程序的执行过程,是个动态概念。
程序运行必须加载在内存中,当有过多的就绪态或阻塞态进程在内存中没有运行,因为内存很小,有可能不足。系统需要把他们移动到内存外磁盘中,称为挂起状态。就绪状态的进程挂起就是挂起就绪状态,阻塞进程挂起就称为阻塞挂起状态。
每个进程的产生都有自己的唯一的 ID 号(pid),并且附带有一个它父进程的 ID 号(ppid)。进程死亡时,ID 被回收。
进程间靠优先级获得 CPU 资源,时间片段轮换来更新优先级,以保证不会一个进程占据 CPU 时间过长。每个进程都得到轮换运行,因为这个时间非常短,所以给我们就好像是系统在同时运行好多进程。
进程状态
二、孤儿进程
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。
子进程死亡需要父进程来处理,那么意味着正常的进程应该是子进程先于父进程死亡。当父进程先于子进程死亡时,子进程死亡时没父进程处理,这个死亡的子进程就是孤儿进程。但孤儿进程与僵尸进程不同的是,由于父进程已经死亡,系统会帮助父进程回收处理孤儿进程。所以孤儿进程实际上是不占用资源的,因为它终究是被系统回收了。不会像僵尸进程那样占用 ID,损害运行系统。
三、僵尸进程
子进程先于父进程退出后,子进程的 PCB 需要其父进程释放,但是父进程并没有释放子进程的 PCB,这样的子进程就称为僵尸进程,僵尸进程实际上是一个已经死掉的进程。
一个进程在调用 exit 命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用 exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。在 Linux 进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。这个僵尸进程需要它的父进程来为它收尸,如果他的父进程没有处理这个僵尸进程的措施,那么它就一直保持僵尸状态,如果这时父进程结束了,那么 init 进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。
如果有大量的僵尸进程驻在系统之中,必然消耗大量的系统资源。但是系统资源是有限的,因此当僵尸进程达到一定数目时,系统因缺乏资源而导致奔溃。所以在实际编程中,避免和防范僵尸进程的产生显得尤为重要。
用 ps 命令可以看到僵尸进程后有一个<defunct> ,defunct 是已死的,僵尸的意思,可以看出这时的子进程已经是一个僵尸进程了。也可以用命令 ps -aux | grep pid 查看该进程状态。
处理方式
如果父进程能及时处理,可能用 ps 命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。如果父进程在子进程结束之前退出,则子进程将由 init 接管。init 将会以父进程的身份对僵尸状态的子进程进行处理。所以僵尸进程会占用资源危害系统。我们应当避免僵尸进程的出现。
解决方式如下:
- 一种比较暴力的做法是将其父进程杀死,那么它的子进程,即僵尸进程会变成孤儿进程,由系统来回收。但是这种做法在大多数情况下都是不可取的,如父进程是一个服务器程序,如果为了回收其子进程的资源,而杀死服务器程序,那么将导致整个服务器崩溃,得不偿失。显然这种回收进程的方式是不可取的,但其也有一定的存在意义。
- SIGCHLD信号处理——wait 函数是用来处理僵尸进程的,但是进程一旦调用了 wait,就立即阻塞自己,由 wait 自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait 就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait 就会一直阻塞在这里,直到有一个出现为止。
wait 函数
#include <sys/types.h> /* 提供类型pid_t的定义,实际就是int型 */ #include <sys/wait.h> pid_t wait(int *status)
参数 status 用来保存被收集进程退出时的一些状态,它是一个指向 int 类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为 NULL,就象下面这样:pid=wait(NULL);如果成功,wait 会返回被收集的子进程的进程 ID,如果调用进程没有子进程,调用就会失败,此时 wait 返回 -1,同时 errno 被置为ECHILD。
由于调用 wait 之后,就必须阻塞,直到有子进程结束,所以,这样来说是非常不高效的,我们的父进程难道要一直等待你子进程完成,最后才能执行自己的代码吗?难道就不能我父进程执行自己的代码,你子进程什么时候完成我就什么时候去处理你,不用一直等你?当然是有这种方式了。
实际上当子进程终止时,内核就会向它的父进程发送一个 SIGCHLD 信号,父进程可以选择忽略该信号,也可以提供一个接收到信号以后的处理函数。对于这种信号的系统默认动作是忽略它。我们不希望有过多的僵尸进程产生,所以当父进程接收到 SIGCHLD 信号后就应该调用 wait 或 waitpid 函数对子进程进行善后处理,释放子进程占用的资源。