Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

僵尸进程

  • 产生原因

    • 子进程在调用exit(n);return n;返回后,会将返回值传递给操作系统,进入终止状态
      • 这时候操作系统并不会销毁子进程
      • 若父进程仍未终止,那处在此状态的子进程就是僵尸进程
        • 可通过ps au命令的STAT列为Z+验证
      • 若父进程终止了,那子进程也会被销毁
  • 一般的解决方法

    • 让父进程主动接收子进程的返回值即可

    • 方法1——使用wait函数

      #include <sys/wait.h>
      
      pid_t wait(int* statloc);
      
      • 此函数会将状态填入statloc指向的地址,若成功则返回接收的子进程ID
      • 父进程到状态后,需要用宏进行分离
        int status;
        wait(&status);
        if (WIFEXITED(status)) { //检查是否正常终止
            printf("Return Value: %d \n", WEXITSTATUS(status)); //获取返回值
        }
        
      • 缺点
        • 调用wait时,如果没有已终止的子进程,那父进程必将阻塞,因此要谨慎使用
          • 若不想阻塞,可以使用方法2并配合WNOHANG使用
    • 方法2——使用waitpid函数

      #include <sys/wait.h>
      
      pid_t wait(pid_t pid, int* statloc, int options);
      
      • pid 等待终止的目标子进程ID
        • 若传递-1,则可等待任意子进程
      • options
        • 建议传递<sys/wait.h>中声明的WNOHANG,可在没有已终止的子进程时,直接返回0,不阻塞父进程
      • 使用例子
        int status;
        while (!waitpid(-1, &status, WNOHANG)) {
            sleep(3);
        }
        if (WIFEXITED(status)) { //检查是否正常终止
            printf("Return Value: %d \n", WEXITSTATUS(status)); //获取返回值
        }
        
  • 更好的解决方法

    • 让父进程监听子进程终止的信号,并触发提前指定的回调函数
    • 旧方法——使用signal函数
      • 不推荐,原因是在不同的Unix系统中可能存在差别
      #include <signal.h>
      
      void (*signal(int signo, void (*func)(int)))(int)
      
      • signal 函数名
      • signo 参数1,代表监听的信号
        • SIGALRM —— 已超过调用alarm函数注册的时间
        • SIGINT —— 键盘输入CTRL+C
        • SIGCHLD —— 子进程终止
      • func 参数2,代表信号发生时调用的回调函数指针
        • 一般传递函数名即可
      • 返回void型函数指针,一般不使用
      • 使用例子
        #include <stdio.h>
        #include <unistd.h>
        #include <signal.h>
        
        void alarmHandler(int sig) {
            if (sig == SIGALRM) puts("Timeout");
            alarm(2);
        }
        int main(int argc, char* argv[]) {
            signal(SIGALRM, alarmHandler);
            alarm(2); //2秒后触发SIGALRM信号
            for(int i = 0; i < 3; i++) {
                puts("sleep");
                sleep(100); //休眠100秒
            }
            return 0;
        }
        
        • 输出结果
          sleep
          Timeout
          sleep
          Timeout
          sleep
          Timeout
          
        • 注意: 信号触发时将会唤醒休眠中的进程,将会立刻进入下一次循环输入sleep
    • 推荐方法——使用sigaction函数
      #include <signal.h>
      
      int sigaction(int signo, const struct sigaction* act, struct sigaction* oldact)
      
      • signo 代表监听的信号
      • act 结构体指针,内含信号发生时调用的回调函数指针
        struct sigaction {
            void (*sa_handler)(int);
            sigset_t sa_mask;
            int sa_flags;
        }
        
      • oldact 可传入指针,获取之前注册的回调函数
      • 等价使用例子
        #include <stdio.h>
        #include <unistd.h>
        #include <signal.h>
        
        void alarmHandler(int sig) {
            if (sig == SIGALRM) puts("Timeout");
            alarm(2);
        }
        int main(int argc, char* argv[]) {
            struct sigaction act;
            act.sa_handler = alarmHandler;
            sigemptyset(&act.sa_mask) //置0
            act.sa_flags = 0; //置0
            sigaction(SIGALRM, &act, 0);
            
            alarm(2); //2秒后触发SIGALRM信号
            for(int i = 0; i < 3; i++) {
                puts("sleep");
                sleep(100); //休眠100秒
            }
            return 0;
        }
        
      • 解决僵尸进程
        void read_childprocess() {
            int status;
            pid_t id = waitpid(-1, &status, WNOHANG)
            if (WIFEXITED(status)) { //检查是否正常终止
                printf("Removed Process ID: %d \n", id);
                printf("Return Value: %d \n", WEXITSTATUS(status)); //获取返回值
            }
        }
        
        int main(int argc, char* argv[]) {
            struct sigaction act;
            act.sa_handler = read_childprocess;
            sigemptyset(&act.sa_mask) //置0
            act.sa_flags = 0; //置0
            sigaction(SIGCHLD, &act, 0);
        
            //下面正常调用fork即可
        }