天天看点

Linux wait()、waitpid()实现进程同步

特殊进程

Linux系统中有两种特殊进程:僵尸进程、孤儿进程;

僵尸进程:当程序调用exit()函数后,该进程并不是马上就消失,
而是留下一个称为僵尸进程的数据结构。僵尸进程是一种特殊的
进程,他几乎放弃进程退出前占用的所有内存,既没有可执行代
码也不能被调度,只在进程列表中留下一个位置,记载进程退出
状态等信息供父进程收集。若父进程中没有回收子进程的代码,
子进程会一直处于僵尸状态。子进程退出后能释放用户区,但
不能释放内核区的一些数据。

孤儿进程:父进程死了,子进程还或者,子进程就变成了孤儿进程。
此时,在较低的一些Linux版本,子进程会被init进程领养,但较高
的一些版本被一个特殊的进程领养;
           

特殊进程的危害

特殊进程不能再次运行。却会占据一定的内存空间,当系统中的僵尸进程数量过多时,不仅会占据系统内存,还会占用进程id;因此使用wait()、waitpid()可以避免僵尸进程产生。

如果僵尸进程已经产生,通常解决僵尸进程的方法就是终止其父进程。这样僵尸进程就会作为孤儿进程被init进程(现在的版本可能不是init进程领养,总之会有一个特殊的系统进程领养),这个特殊的进程会不断调用wait()函数来获取子进程的状态,收集退出的子进程的状态,并清理它占用的空间。最后,孤儿进程永远不可能称为僵尸进程;

wait()函数

返回值:调用成功,返回捕捉到的僵尸态子进程的id;调用失败,返回-1,errno被设置为ECHILD;

参数status:status是一个传出参数,用于获取子进程的退出状态,如果不关心子进程的退出状态可以传入NULL;

调用wait()函数的进程会被挂起,进入阻塞状态。直到子进程变成僵尸进程,wait()函数捕捉到子进程的退出状态才会转变到运行状态,回收子进程的资源并返回;若没有变为僵尸态的子进程,wait()函数会让进程一直阻塞。当前进程如果有多个子进程,只要捕捉到一个变为僵尸态的子进程信息,wait()函数就会返回子进程id恢复执行状态;

子进程的退出状态被存放在exit()函数的status参数的低八位中。使用常规方法读取比较麻烦,所以Linux定义了两个宏,用于获取进程退出状态。

WIFEXITED(status);用于判断子进程是否正常退出,如果是,返回非零值,否则返回零;

WEXITSTATUS(status);WIFEXITED()函数通常与WEXITSTATUS()函数搭配使用;若WIFEXITED()返回非0(即正常退出),则可以使用WEXITSTATUS()提取子进程的返回值;

WIFSIGNALED(status) 如果子进程因为一个未捕获的信号而终止,它就返回真;否则返回假。

WTERMSIG(status) 如果WIFSIGNALED(status)为真,则可以用该宏获得导致子进程终止的信号代码。

WIFSTOPPED(status) 如果当前子进程被暂停了,则返回真;否则返回假。

WSTOPSIG(status) 如果WIFSTOPPED(status)为真,则可以使用该宏获得导致子进程暂停的信号代码。

waitpid()函数

pid>0时,只等待pid与该参数相同的子进程,如果该子进程一直没有退出,那么父进程会一直阻塞;

pid=0时,会等待同一个进程组的子进程,若子进程加入了其他进程组,waitpid()不再关心它的状态;

pid=-1时,waitpid()与wait()函数相同,将阻塞等待并回收一个子进程;

pid<-1时,会等待指定进程组的任何子进程,进程组的id等于pid的绝对值;

int options

参数options提供了一些另外的选项来控制waitpid()函数的行为。如果不想使用这些选项,则可以把这个参数设为0。

主要使用的有以下两个选项:

WNOHANG 如果pid指定的子进程没有结束,则waitpid()函数立即返回0,而不是阻塞在这个函数上等待;如果结束了,则返回该子进程的进程号;

WUNTRACED 如果子进程进入暂停状态,则马上返回。

这些参数可以用“|”运算符连接起来使用。

如果waitpid()函数执行成功,则返回子进程的进程号;如果有错误发生,则返回-1,并且将失败的原因存放在errno变量中。

失败的原因主要有:没有子进程(errno设置为ECHILD),调用被某个信号中断(errno设置为EINTR)或选项参数无效(errno设置为EINVAL)

如果像这样调用waitpid函数:waitpid(-1, status, 0),这此时waitpid()函数就完全退化成了wait()函数。

继续阅读