一、进程
进程可以看做程序的一次执行过程。在linux下,每个进程都会被分配一个唯一的数字编号,我们称之为进程标识符或PID。PID是一个从1到32768的正整数,其中1一般是特殊进程init,其它进程从2开始依次编号。当用完32768后,从2重新开始。
在一台单处理器的计算机上,同一时间只有一个进程可以运行,其他程序处于就绪状态,该状态表示程序只要得到CPU就可以运行了。每个进程运行的时间是有限的,我们称作时间片,时间片是相当短暂的,这就给我们一种程序在同时运行的错觉。Linux内核用进程调度器来决定下一个时间片分配给那个进程,它的判断依据是根据进程的优先级。高优先级的进程运行得更频繁,但长期不间断运行的进程,优先级会变低,同时,低优先级的进程随着时间的推移,优先级会不断提升,直到其能够被执行。
二、父进程与子进程
子进程指的是由另一进程(对应称之为父进程)所创建的进程。一个进程可能下属多个子进程,但最多只能有1个父进程,而若某一进程没有父进程,则可知该进程很可能由内核直接生成。
三、孤儿进程
父进程先结束,子进程还在运行,该子进程就叫做孤儿进程。 孤儿进程会被 init 进程领养,init 进程就变成了该孤儿进程的父进程。
这样做是为了释放子进程占用的系统资源。因为进程结束之后,能够自己释放用户区所占用的内存空间,但它释放不了PCB(进程控制块)所占的系统资源,该资源必须由父进程释放。
四、僵尸进程
子进程先于父进程结束,而父进程又没有回收子进程,释放子进程占用的系统资源,此时子进程将成为一个僵尸进程。
如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
五、例子
上面的这道程序会使父函数阻塞10秒,此时子进程早已结束,而父进程无任何作为,这时该子进程就会变成僵尸进程。
由上图我们可以看到,光标还在不停闪烁,表示在子进程运行完输出 B 后,父进程还在运行,于是子进程后面有了一个 defunct 标志,这就说明它现在是一个僵尸进程。
当然,当父进程正常结束后,init 进程像收养孤儿进程一样负责回收该僵尸进程占用的资源,如此僵尸进程就不存在了。
四、僵尸进程的应对方法
1、wait()函数
wait()函数一般用在父进程中等待回收子进程的资源,而防止僵尸进程的产生。
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status); //阻塞函数,其条件是有子进程结束
参数:
status:传出参数。判断子进程是如何结束的,正常结束还是被某个信号杀死了。
若设置为NULL,则说明不关心该子进程的退出情况,只是想释放其资源。
使用相应的宏获取子进程的退出状态:
WIFEXITED(status):该值非0,进程正常退出。
WEXITSTATUS(status):若上面的宏为真。使用此宏获取进程的退出状态,即exit(n)中n这个数字
例如: if(WIFEXITED){printf("%d",WEXITSTATUS(STATUS));} //打印出进程的退出码
WIFSIGNALED(status):该值非0,进程异常终止。
WTERMSIG(status):若上面的宏为真,使用此宏获取使子进程退出的信号的编号
例如:if(WIFSIGNALED(status)){"%d",WTERMSIG(status);} //打印出该信号的编号
返回值:
-1:回收失败,说明已经没有子进程了。如果还有子进程存在,该函数会一直阻塞
>0: 回收的子进程pid
注意:
wait()函数一次只能回收一个已结束的子进程。若程序有多个子进程,则需要循环调用该函数。因为wait的返回值是终止进程的进程PID,所以父进程总能知道哪一个子进程终止了。
例子:
如上面的程序,我们只是添加了一个wait()函数,这时候即使子进程结先束了,父进程还在运行,是不会出现僵尸进程的,因为当子进程结束后,wait()函数将其回收了。
4025 是wait()函数返回的回收的子进程pid
注意:wait()的参数一定要写,即使不需要也要把NULL写进去,否则返回值会已一直是 -1,因为出错了。
2、waitpid()函数
pid_t waitpid(pid_t pid, int* status, int options);
参数:
pid:
等于-1: 回收任一子进程,与wait()函数等效
大于0: 回收其进程ID与参数pid相等的子进程
等于0 :回收当前进程组任一子进程
大于 -1: 回收其组ID等于参数pid的绝对值的任一子进程。
status:子进程的退出状态,用法同wait()函数的参数
options:设置为WNOHANG,则函数为非阻塞的; 设置为0,函数为阻塞的
返回值:
>0: 返回回收的子进程的pid
-1:无子进程、调用被某个信号中断、选项参数无效等
0:参数options被设置为WNOHANG,且子进程正在运行。
因为waitpid()函数在不设置第三个参数的情况下是阻塞的,所以可以用来等待某个特定进程的结束。
wait与waitpid区别:
- 在一个子进程终止前, wait()使其调用者阻塞,而waitpid()的第三参数若设置为WNOHANG,可使调用者不阻塞。
- waitpid()并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的特定进程。
- 实际上wait()函数可以看做是waitpid()函数的一个特例。waitpid(-1,&status, 0);
3、SIGCHLD信号
子进程结束时,会向父进程发送一个 SIGCHLD 信号,系统默认是忽略这个信号,所以只要父进程对这个信号进行捕获,并在信号的捕捉函数中调用 wait() 函数或 waitpid() 函数以取得进程ID和其终止状态,释放其资源,就可以保证不出现僵尸进程。