进程的状态
在操作系统中,我们学习过进程的几种状态,分别为就绪态、运行态和阻塞态。然而在实际的系统中,往往还会有两种状态,即新建态和终止态。 因此,一共有新建态、就绪态、运行态、阻塞态和终止态 5 种进程状态。
Linux 下使用 ps 能够查询的状态
在 linux 和 unix 中,可以使用 ps -l
命令来查看进程状态,在实际的操作系统中,进程的状态要比预期的复杂的多。
1 | D 不可中断睡眠 (通常是在IO操作) 收到信号不唤醒和不可运行, 进程必须等待直到有中断发生 |
fork 产生子进程
在多线程中,我们需要生成新的进程,会通过使用 fork
来产生子进程。在子进程产生后,如果父进程处理方式不正确或者提前退出,则可能会出现僵尸进程和孤儿进程。
在进程运行过程中,会将进程的一些信息(进程号,执行时间,退出码,打开的文件等)存入进程表,进程结束时会传递给父进程,父进程调用 wait
或者 waitpid
来释放符号表中的信息。
孤儿进程
在父进程创建了子进程后,子进程会在结束时将进程表中的信息传递给父进程,然后将进程表中的信息移除。当子进程还没有结束,父进程提前退出时,子进程这时就被称为”孤儿进程”。孤儿进程会被进程号为1的init
进程收养,代替其父进程完成善后工作。因此,孤儿进程不会对系统造成危害。
僵尸进程
僵尸进程产生的原因
在父进程创建子进程后,子进程已经结束,但是仍然占用符号表的状态,被称作”僵尸进程”。 这一过程任何进程都会遇到(除了 init 进程),但是如果父进程立即进行处理,这一时间会很短暂的。如果父进程迟迟不做处理,这时候就可以看到很多状态为 Z 的僵尸进程。
僵尸进程无疑是有危害的,因为僵尸进程长时间占用进程号,如果系统长时间运行,可能会导致进程号耗尽的情况。
如何消除僵尸进程
僵尸进程本身已经结束了,所以调用 kill
命令去结束僵尸进程是没有用的。其实僵尸进程的根源是父进程,因为父进程不去收养这些进程而形成了长时间存在的僵尸进程。
因此,杀死父进程可以结束僵尸进程。父进程被杀死后,init
进程会收养这些进程并移除其进程表中的信息。
使用两次 fork 避免僵尸进程
当我们 fork()
一次后,存在父进程和子进程。这时有两种方法来避免产生僵尸进程:
- 父进程调用
waitpid()
等函数来接收子进程退出状态。 - 父进程先结束,子进程则自动托管到 init 进程(pid = 1)
第一种方法,有两种情况:
- 父进程在创建子进程后等待子进程结束,然后执行自身业务 => 如果子进程时间比较长,父进程的业务无法完成;
- 父进程在执行自己业务后等待子进程结束 => 子进程会长时间处于僵尸进程状态。
第二种方法父进程如果有自身业务将无法进行,不可取。
因此,我们可以通过两次 fork 来避免僵尸进程。
- 第一次 fork: 父进程产生子进程 ,等待子进程结束;
- 第二次 fork: 子进程产生孙子进程;
- 子进程立即退出,孙子进程变成孤儿进程;
- 父进程收到子进程结束信号,继续执行后续逻辑;
这样,孙子进程由 init 进程负责收尸,子进程在创建孙子进程后立即由父进程收尸,便不会产生僵尸进程的问题了。