wait()函数的详细分析

之前一直没太深入的去理解wait()函数,今天机缘巧合之前又看了看,发现之前没有真正的理解该函数。

众所周知,wait()函数一般用在父进程中等待回收子进程的资源,而防止僵尸进程的产生。

(In UNIX System terminology, a process that has terminated, but whose parent has not yet waited for it, is called a zombie. )

下面我就带着问题,层层深入地来分析这个函数。

当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。
因为子进程终止是个异步事件(这可以在父进程运行的任何是否发生),所以这种信号也是内核向父进程发的异步通知。
父进程可以选择忽略该信号,或者提供一个信号处理程序。
对于这种信号的系统默认动作是忽略。

调用wait()或waitpid()的父进程会发生什么情况:
a. 如果其所有子进程都还在运行,则阻塞;
  Q1:如果是一部分子进程终止,而另一部分还在运行,那么父进程还会阻塞吗?
    不会,只要有一个进程终止,wait就会返回。也就是说只要wait接收到一个SIGCHLD信号,wait()就会返回。对于两个或多个子进程的情况,需要调用wait两次或多次
b. 如果一个子进程已经终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回;
c. 如果它没有任何子进程,则立即出错返回;

int wait(int* statloc);
int waitpid(pid_t pid, int* statloc, int options);

这两个函数的区别如下:
1. 在一个子进程终止前,wait使其调用者阻塞,而waitpid有一个选项,可使调用者不阻塞;
2. waitpid()并不等待在其调用之后的第一个终止的子进程,它有若干个选项,可以控制它所等待的进程;

如果一个子进程已经终止,并且是一个僵尸进程,则wait立即返回并取得该子进程的终止状态,否则wait使其调用者阻塞直到一个子进程终止。
如果调用者阻塞而且它有多个子进程,则在其一个子进程终止时,wait就立即返回。
因为wait的返回值是终止进程的进程ID,所以父进程总能知道哪一个子进程终止了。

参数statloc如果不是一个空指针,则终止进程的终止状态就存放在statloc所指向的单元。
参数statloc如果是一个空指针,则表示父进程不关心子进程的终止状态。

Q2:statloc中不同的值具体表示什么含义呢?
wait的输出参数statloc中,某些位表示退出状态(正常返回),其它位则指示信号编号(异常返回),有一位指示是否产生了一个core文件等等。

有四个互斥的宏可用来取得进程终止的原因。
1. WIFEXITED(status):若为正常终止子进程返回的状态,则为真。WEXITSTATUS(status)可取得子进程传送给exit、_exit或_Exit参数的低8位;
2. WIFSIGNALED(status):若为异常终止子进程返回的状态,则为真(接到一个不捕捉的信号,如SIGCHLD)。对于这种情况,可执行WTERMSIG(status),取得是子进程终止的信号的编号。
另外,有些实现(非Single UNIX Specification)定义宏WCOREDUMP(status),若已产生终止进程的core文件,则它返回真。
3. WIFSTOPPED(status):若为当前暂停的子进程的返回的状态,则为真。对于这种情况,可执行WSTOPSIG(status),取使子进程暂停的信号编号。
4. WIFCONTINUED(status):若在作业控制暂停后已经继续的子进程返回了状态,则为真.(POSIX.1的XSI扩展,仅用于waitpid)

Q3: wait函数和SIGCHLD信号的关系?两者之间的关系,需要分成三个问题

已知系统默认是忽略SIGCHLD信号,在一个进程终止或停止时,会将SIGCHLD信号发送给其父进程。
已知父进程若不调用wait()获取子进程的终止状态,那么子进程就会变成僵尸进程。

Q3.1:wait()是关于是否产生僵尸进程的问题。

Q3.2:SIGCHLD信号是关于自己本身的处理方式的选择问题。

当这两个问题(Q3.1&Q3.2)结合在一起应用时,就产生了另外一个问题,父进程是同步还是异步的问题(或者描述为阻塞还是非阻塞问题)
当SIGCHLD的处理方式是系统默认时,父进程调用了wait()以防止子进程变成僵尸进程,那么父进程必须等待子进程结束之后才能执行wait()之后的流程,即同步问题。
当SIGCHLD的处理方式是捕获时,在其信号处理程序中调用wait()函数,就能获取子进程的终止状态而不产生僵尸进程同时父进程并不会阻塞,做自己想做的事,即异步问题。

Q3.3:wait什么时候返回的问题,wait()的返回和SIGCHLD有什么关系?

根据wait()的描述:
All of these system calls are used to wait for state changes in a child of the calling process, and obtain information about the child whose
state has changed. A state change is considered to be: the child terminated; the child was stopped by a signal; or the child was resumed by a
signal. In the case of a terminated child, performing a wait allows the system to release the resources associated with the child; if a wait is
not performed, then terminated the child remains in a "zombie" state (see NOTES below).

If a child has already changed state, then these calls return immediately. Otherwise they block until either a child changes state or a signal
handler interrupts the call (assuming that system calls are not automatically restarted using the SA_RESTART flag of sigaction(2)). In the
remainder of this page, a child whose state has changed and which has not yet been waited upon by one of these system calls is termed waitable.
当子进程的状态发生改变时,wait()返回;
当调用wait()的进程接收到一个被设置为SA_INTERRUPT的信号时,wait()返回;
因为SIGCHLD信号的产生必然是伴随着子进程状态的改变,所以当有SIGCHLD信号发生时,wait会返回。

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <sys/wait.h>
 4 #include <errno.h>
 5 #include <signal.h>
 6
 7 void print_exit(int status)
 8 {
 9     if (WIFEXITED(status))
10         printf("normal termination, exit status = %d\n", WEXITSTATUS(status));
11     else if (WIFSIGNALED(status))
12         printf("abnormal termination, signal number = %d%s\n", WTERMSIG(status),
13 #ifdef WCOREDUMP
14         WCOREDUMP(status) ? ("core file generated") : (""));
15 #else
16     "");
17 #endif
18     else if (WIFSTOPPED(status))
19         printf("child stopped, signal number=%d\n", WSTOPSIG(status));
20 }
21
22 void sig_child(int signo)
23 {
24     int status;
25     int ret;
26     ret = wait(&status);
27     printf("pid:%d, res:%d, status=%d, %s\n", getpid(), ret, status, strerror(errno));
28     print_exit(status);
29 }
30
31 void sig_usr(int signo)
32 {
33     if (signo == SIGUSR1)
34         printf("received SIGUSR1\n");
35     else if (signo == SIGUSR2)
36         printf("received SIGUSR2\n");
37     else
38         printf("received signal %d\n", signo);
39 }
40
41
42 int main(int argc, char** argv)
43 {
44     pid_t pid;
45     int status;
46     int ret;
47     int remaintime=0;
48     struct sigaction act, oact;
49     sigset_t oldmask;
50
51     //signal(SIGCHLD, sig_child);
52     //signal(SIGUSR1, sig_usr);
53
54     act.sa_handler = sig_usr;
55     sigemptyset(&act.sa_mask);
56     act.sa_flags = 0|SA_INTERRUPT;
57     sigaction(SIGUSR1, &act, &oact);
58
59     if ((pid=fork()) < 0)
60     {
61         printf("fork error\n");
62         return -1;
63     }
64     else if (pid == 0)
65     {
66
67         printf("child:pid:%d\n", getpid());
68         remaintime = sleep(200);
69         printf("remiantime=%d\n", remaintime);
70         //exit(0);
71         //return 0;
72         //
73         //sleep(30);//SIGQUIT
74     }
75     else
76     {
77         printf("father:pid:%d\n", getpid());
78         //while(1)
79         //{
80         //    sleep(1);
81         //    printf("1111\n");
82         //}
83         ret = wait(&status);
84         printf("res:%d, status=%d, %s\n", ret, status, strerror(errno));
85         print_exit(status);
86     }
87
88     return 0;
89 }
时间: 2024-10-24 23:15:00

wait()函数的详细分析的相关文章

device_create 函数详细分析

原文地址:device_create 函数详细分析 作者:liujunwei1234 我们在刚开始写Linux设备驱动程序的时候,很多时候都是利用mknod命令手动创建设备节点,实际上Linux内核为我们提供了一组函数,可以用来在模块加载的时候自动在/dev目录下创建相应设备节点,并在卸载模块时删除该节点,当然前提条件是用户空间移植了udev. 内核中定义了struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)

关于Delphi中的字符串的详细分析

关于Delphi中的字符串的详细分析 只是浅浅的解析下,让大家可以快速的理解字符串. 其中的所有代码均在Delphi7下测试通过. Delphi 4,5,6,7中有字符串类型包括了: 短字符串(Short String) 长字符串(Long String) 宽字符串(Wide String) 零结尾字符串(Null-Terminated String).PChar和字符数组 1.短字符串(Short String) 固 定长度,最大字符数个数为255,短字符串也成为长度字节(Length-byt

MVC之前的那点事儿系列(5):HttpPipeline详细分析(下)(转载)

MVC之前的那点事儿系列(5):HttpPipeline详细分析(下) 文章内容 接上面的章节,我们这篇要讲解的是Pipeline是执行的各种事件,我们知道,在自定义的HttpModule的Init方法里,我们可以添加自己的事件,比如如下代码: public class Test : IHttpModule { public void Init(HttpApplication context) { context.BeginRequest += new EventHandler(context_

MVC之前的那点事儿系列(4):HttpPipeline详细分析(上)(转载)

MVC之前的那点事儿系列(4):HttpPipeline详细分析(上) 文章内容 继续上一章节的内容,通过HttpApplicationFactory的GetApplicationInstance静态方法获取实例,然后执行该实例的BeginProcessRequest方法进行执行余下的Http Pipeline 操作,代码如下: // Get application instance IHttpHandler app = HttpApplicationFactory.GetApplication

Yarn之ResourceManager详细分析笔记(一)待续

一.概述     本文将介绍ResourceManager在Yarn中的功能作用,从更细的粒度分析RM内部组成的各个组件功能和他们相互的交互方式. 二.ResourceManager的交互协议与基本职能 1.ResourceManager交互协议 在整个Yarn框架中主要涉及到7个协议,分别是ApplicationClientProtocol.MRClientProtocol.ContainerManagementProtocol.ApplicationMasterProtocol.Resour

MVC之前的那点事儿系列(4):Http Pipeline详细分析(上)

文章内容 继续上一章节的内容,通过HttpApplicationFactory的GetApplicationInstance静态方法获取实例,然后执行该实例的BeginProcessRequest方法进行执行余下的Http Pipeline 操作,代码如下: // Get application instance IHttpHandler app = HttpApplicationFactory.GetApplicationInstance(context); 那GetApplicationIn

LeetCode详细分析 :: Recover Binary Search Tree [Tree]

Recover the tree without changing its structure. Note: A solution using O(n) space is pretty straight forward. Could you devise a constant space solution? confused what "{1,#,2,3}" means? > read more on how binary tree is serialized on OJ. 这里

详细分析contrex-A9的汇编代码__switch_to(进程切换)

//函数原型:版本linux-3.0.8 struct task_struct *__switch_to(structtask_struct *, struct thread_info *, struct thread_info *); #define switch_to(prev,next,last)                                       \ do {                                                     

第1阶段——uboot启动函数bootm命令分析(9)

本节主要学习: 详细分析UBOOT中"bootcmd=nand read.jffs2 0x30007FC0 kernel;bootm 0x30007FC0"中怎么实现bootm命令启动内核. 其中bootm要做的事情:a 读取头部,把内核拷贝到合适的地方(0X30008000)b 在do_boom_linux()中把参数给内核准备好,并告诉内核参数的首地址c 在do_boom_linux()中最后使用theKernel () 引导内核. {注意:当在cmd_bootm.C中没有定义宏C