• 进程篇(3: 基本进程控制:进程的退出)--请参照本博客“操作系统”专栏


    1. exit函数:

    进程的五种正常的结束方式:

    • 在main函数中执行return语句,这等效于exit;
    • 调用exit函数。此函数由ISO C定义,其操作包括运行各终止处理程序,然后关闭所有标准I/O流等。
    • 调用_exit或_Exit函数,ISO C定义了_Exit函数,目的是为了为进程提供一种无需运行终止处理程序和信号处理程序而终止的方法。并不处理标准I/O流!
    • 进程的最后一个线程在其启动例程中执行返回语句,然后该进程以终止状态0返回。
    • 进程的最后一个线程调用pthread_exit函数。

    进程的三种非正常的结束方式:

    • 调用abort。它产生SIGABRT信号,这是下一种异常终止的一个特例。
    • 当进程接收到某些信号时,信号可以由进程自身,其他进程或内核产生。比如进程越过其地址空间访问存储单元或除以0,内核就会为该进程产生相应的信号!
    • 最后一个线程对"取消"(cancellation)请求做出响应。按系统默认,"取消"以延迟方式发生:一个线程要求取消另一个线程,一段时间后,目标线程终止。

    不管进程如何终止,最后都会执行内核中的同一段代码:这段代码为进程关闭所有的相应的打开描述符,释放它所用的存储器等。

    终止进程的父进程可以用wait和waitpid取得其终止状态。

    对于父进程已经终止的进程,它们的父进程都被改为init进程,称这些进程被init进程领养。其操作过程如下:

    在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止进程的子进程,如果是,那么将该进程的父进程的id更改为1(init进程ID)。

    这种进程保证了每个进程都有一个父进程。

    另外一个问题是:如果子进程在父进程之前终止,那么父进程如何如何得到子进程的终止状态呢?

    回答: 内核为每个终止进程保存了一定量的信息,所以当终止进程的父进程调用wait和waitpid,时可以得到这些信息。这些信息至少包括:进程ID,该进程的终止状态,

    以及该进程使用CPU时间总量。在UNIX术语中,一个已经终止,但是其父进程尚未对其进行善后处理(终止进程的相关信息,释放它们仍然占用的资源)的进程被称为僵死进程

    上图是一个典型的UNIX/LINUX 进程状态模型。

    2. wait 和 waitpid 函数:

      当一个进程正常或异常终止的时候,内核就会向其父进程发送SIGCHLD信号。因为子进程的终止是个异步事件(可以在父进程运行的任何时候发生),所以这种信号也是内核向父进程发的异步通知。父进程有两种处理这个信号的方式: 1)忽略该信号; 2) 提供一个该信号发生时即被调用执行的函数(信号处理程序)。

      调用wait或waitpid的进程可能会发生的情况:

    • 如果所有的子进程都还在运行,那么这个调用进程阻塞;
    • 如果一个子进程已经终止,正在等待其父进程获取其终止状态,则取得该子进程的终止状态之后立即返回;
    • 如果它没有任何子进程,则立即出错返回;

    下面是linux manpage 中对wait函数族的描述:

    NAME
           wait, waitpid, waitid - wait for process to change state
    
    SYNOPSIS
           #include <sys/types.h>
           #include <sys/wait.h>
    
           pid_t wait(int *status);
    
           pid_t waitpid(pid_t pid, int *status, int options);
    
           int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
                           /* This is the glibc and POSIX interface; see
                              NOTES for information on the raw system call. */
    
       Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
    
           waitid():
               _SVID_SOURCE || _XOPEN_SOURCE >= 500 ||
               _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED
               || /* Since glibc 2.12: */ _POSIX_C_SOURCE >= 200809L
    
    DESCRIPTION
           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 ter‐minated (子进程终止); 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 the terminated child remains in a "zombie" state (see NOTES below). If a child has already changed state, then these calls return immedi‐ ately. 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() and waitpid() The wait() system call suspends execution of the calling process until one of its children terminates. The call wait(&status) is equivalent to: waitpid(-1, &status, 0); The waitpid() system call suspends execution of the calling process until a child specified by pid argument has changed state. By default, waitpid() waits only for terminated children, but this behavior is mod‐ ifiable via the options argument, as described below. The value of pid can be: < -1 meaning wait for any child process whose process group ID is equal to the absolute value of pid. -1 meaning wait for any child process. 0 meaning wait for any child process whose process group ID is equal to that of the calling process. > 0 meaning wait for the child whose process ID is equal to the value of pid. The value of options is an OR of zero or more of the following con‐ stants: WNOHANG return immediately if no child has exited. WUNTRACED also return if a child has stopped (but not traced via ptrace(2)). Status for traced children which have stopped is provided even if this option is not specified. WCONTINUED (since Linux 2.6.10) also return if a stopped child has been resumed by delivery of SIGCONT. (For Linux-only options, see below.) If status is not NULL, wait() and waitpid() store status information in the int to which it points. This integer can be inspected with the following macros (which take the integer itself as an argument, not a pointer to it, as is done in wait() and waitpid()!): WIFEXITED(status) returns true if the child terminated normally, that is, by call‐ ing exit(3) or _exit(2), or by returning from main(). WEXITSTATUS(status) returns the exit status of the child. This consists of the least significant 8 bits of the status argument that the child specified in a call to exit(3) or _exit(2) or as the argument for a return statement in main(). This macro should be employed only if WIFEXITED returned true. WIFSIGNALED(status) returns true if the child process was terminated by a signal. WTERMSIG(status) returns the number of the signal that caused the child process to terminate. This macro should be employed only if WIFSIGNALED returned true. WCOREDUMP(status) returns true if the child produced a core dump. This macro should be employed only if WIFSIGNALED returned true. This macro is not specified in POSIX.1-2001 and is not available on some UNIX implementations (e.g., AIX, SunOS). Only use this enclosed in #ifdef WCOREDUMP ... #endif. WIFSTOPPED(status) returns true if the child process was stopped by delivery of a signal; this is possible only if the call was done using WUN‐ TRACED or when the child is being traced (see ptrace(2)). WSTOPSIG(status) returns the number of the signal which caused the child to stop. This macro should be employed only if WIFSTOPPED returned true. WIFCONTINUED(status) (since Linux 2.6.10) returns true if the child process was resumed by delivery of SIGCONT. waitid() The waitid() system call (available since Linux 2.6.9) provides more precise control over which child state changes to wait for. The idtype and id arguments select the child(ren) to wait for, as fol‐ lows: idtype == P_PID Wait for the child whose process ID matches id. idtype == P_PGID Wait for any child whose process group ID matches id. idtype == P_ALL Wait for any child; id is ignored. The child state changes to wait for are specified by ORing one or more of the following flags in options: WEXITED Wait for children that have terminated. WSTOPPED Wait for children that have been stopped by delivery of a signal. WCONTINUED Wait for (previously stopped) children that have been resumed by delivery of SIGCONT. The following flags may additionally be ORed in options: WNOHANG As for waitpid(). WNOWAIT Leave the child in a waitable state; a later wait call can be used to again retrieve the child status information. Upon successful return, waitid() fills in the following fields of the siginfo_t structure pointed to by infop: si_pid The process ID of the child. si_uid The real user ID of the child. (This field is not set on most other implementations.) si_signo Always set to SIGCHLD. si_status Either the exit status of the child, as given to _exit(2) (or exit(3)), or the signal that caused the child to termi‐ nate, stop, or continue. The si_code field can be used to determine how to interpret this field. si_code Set to one of: CLD_EXITED (child called _exit(2)); CLD_KILLED (child killed by signal); CLD_DUMPED (child killed by signal, and dumped core); CLD_STOPPED (child stopped by signal); CLD_TRAPPED (traced child has trapped); or CLD_CONTINUED (child continued by SIGCONT). If WNOHANG was specified in options and there were no children in a waitable state, then waitid() returns 0 immediately and the state of the siginfo_t structure pointed to by infop is unspecified. To distin‐ guish this case from that where a child was in a waitable state, zero out the si_pid field before the call and check for a nonzero value in this field after the call returns. RETURN VALUE wait(): on success, returns the process ID of the terminated child; on error, -1 is returned. waitpid(): on success, returns the process ID of the child whose state has changed; if WNOHANG was specified and one or more child(ren) speci‐ fied by pid exist, but have not yet changed state, then 0 is returned. On error, -1 is returned. waitid(): returns 0 on success or if WNOHANG was specified and no child(ren) specified by id has yet changed state; on error, -1 is returned. Each of these calls sets errno to an appropriate value in the case of an error. ERRORS ECHILD (for wait()) The calling process does not have any unwaited-for children. ECHILD (for waitpid() or waitid()) The process specified by pid (wait‐ pid()) or idtype and id (waitid()) does not exist or is not a child of the calling process. (This can happen for one's own child if the action for SIGCHLD is set to SIG_IGN. See also the Linux Notes section about threads.) EINTR WNOHANG was not set and an unblocked signal or a SIGCHLD was caught; see signal(7). EINVAL The options argument was invalid.

    wait函数和waitpid函数的区别在于:

    • 在一个子进程终止前,wait使其调用者阻塞;而waitpid有一个选项,可以使调用者不阻塞。
    • waitpid并不等待其调用之后的第一个终止子进程,它有若干个选项可以控制它所等待的进程。

     下面通过实例来验证wait函数的使用:

     1 #include "apue.h"
     2 #include <sys/wait.h>
     3 
     4 void pr_exit(int status);
     5 
     6 int main(void)
     7 {
     8     pid_t    pid;
     9     int     status;
    10 
    11     if((pid = fork()) < 0)
    12         err_sys("fork error");
    13     else if(pid == 0)
    14         exit(7);
    15 
    16     if(wait(&status) != pid)
    17         err_sys("wait error");
    18     pr_exit(status);
    19 
    20     if((pid = fork()) < 0)
    21         err_sys("fork error");
    22     else if(pid == 0)
    23         abort();
    24 
    25     if(wait(&status) != pid)
    26         err_sys("wait error");
    27     pr_exit(status);
    28 
    29     
    30     if((pid = fork()) < 0)
    31         err_sys("fork error");
    32     else if(pid == 0)
    33         status /= 0;
    34 
    35     if(wait(&status) != pid)
    36         err_sys("wait error");
    37     pr_exit(status);
    38     exit(0);
    39 }
    40 
    41 void pr_exit(int status)
    42 {
    43     if(WIFEXITED(status))
    44         printf("normal termination, exit status = %d
    ",WEXITSTATUS(status));
    45     else if(WIFSIGNALED(status))
    46         printf("abnormal termination, signal number = %d%s
    ",WTERMSIG(status),
    47 #ifdef WCOREDUMP
    48                 WCOREDUMP(status) ? " (core file generated)" : "");
    49 #else
    50             "");
    51 #endif
    52     else if(WIFSTOPPED(status))
    53         printf("child stopped, signal number = %d
    ",WSTOPSIG(status));
    54 }

    上面代码运行的结果是:

    normal termination, exit status = 7
    abnormal termination, signal number = 6
    abnormal termination, signal number = 8

     下面我们来讨论waitpid的作用:

      如果一个进程有多个子进程,那么只要有一个子进程终止,wait就返回。那么,如果要等待某一个特定的子进程的终止呢?(假设我们知道特定的等待进程的ID)

    早期的UNIX版本是这样进行的:

         

    上图反映的是第一次调用wait来等待特定进程终止的流程图,当以后在调用wait来等待另外一个进程的终止时,查看终止进程列表,若找到直接返回,否则继续执行上述过程。

    waitpid 函数中的 pid参数的解释如下:

    • pid == -1; 等待任一子进程,与wait函数等效;
    • pid > 0   ; 等待其ID等于pid的进程的返回;
    • pid == 0 ; 等待其组ID和调用进程组ID相同的进程的返回;
    • pid < -1   ; 等待其组ID等于pid绝对值的进程的返回;

    下面通过一个实例来说明如何使用两次fork来避免僵死进程:

     1 #include "apue.h"
     2 #include <sys/wait.h>
     3 
     4 int
     5 main(void)
     6 {
     7     pid_t pid;
     8 
     9     if ((pid = fork()) < 0)
    10         err_sys("fork error");
    11     else if (pid == 0)            /* first child */
    12     {
    13         if ((pid = fork()) < 0)
    14             err_sys("fork error");
    15         else if (pid > 0)
    16             exit(0);            /* parent from second fork == first child */
    17 
    18     /*
    19      * we're the second child; our parent becomes init as soon as 
    20      * our real parent calls exit() in the statement above.
    21      * Here's where we'd continue excuting, knowing that when
    22      * we're down, init will reap our status
    23      */ 
    24         sleep(2);
    25         printf("Second child, parent pid = %d
    ",getppid());
    26         exit(0);
    27     }
    28 
    29     if(waitpid(pid,NULL,0) != pid)  /* wait for first child  */
    30         err_sys("waitpid error");
    31 
    32     /*
    33      * we're the parent (the original process); we continue executing,
    34      * knowing that we're not the parent of the second child!
    35      */
    36     exit(0);
    37 }

    结果:

    Second child, parent pid = 1

    可见子进程的父进程退出后子进程被init进程托管。

     
  • 相关阅读:
    free
    Lynyrd Skynyrd
    Tree 园丁的烦恼
    On Changing Tree
    Path Queries
    Java开发中的23种设计模式详解(转)
    cas单点登录实现
    Java内存溢出详解
    java多线程并发
    java代码实现图片处理功能。对图片质量进行压缩。
  • 原文地址:https://www.cnblogs.com/jiangheng/p/3763716.html
Copyright © 2020-2023  润新知