1、进程退出

退出方式有两种:正常退出,异常退出

正常退出有5种:

1、Main函数调用return
2、进程调用exit(),标准C库
3、进程调用_exit()或者_Exit(),属于系统调用
和线程有关的

4、进程最后一个线程返回
5、最后一个线程调用pthread_exit

异常退出有三种:
1、调用abort
2、当进程收到某些信号时,如Ctrl + C
3、最后一个线程对取消(cancellation),请求作出响应

不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。对上述任意一种终止情形、我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit._exit和_Exit),实现这一点的方法是,将其退出状态((exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination status)。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数,取得其终止状态。

二、子进程退出的信息收集

为什么要等待子进程退出
1、防止僵尸进程,造成内存泄漏
2、父进程要管理子进程,所以父进程交代给子进程的任务完成的如何,都需要知道,如,子进程运行完成,运行结果对还是不对,或者是否正常退出
3、父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

如果不收集子进程的退出信息会怎么样?

如果子进程的退出状态不被收集,子进程会变成僵死进程(僵尸进程)。

例如

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int data = 3;
    pid_t pid;
    pid = vfork();
    if(pid > 0){
       while(1){
           printf("Father pid = %d\n",getpid());
           sleep(1);
       }
    }else if(pid == 0){
    	while(1){
        	data--;
            printf("Child pid = %d\n",getpid());
            sleep(3);
            if(data == 0){
            	exit(0);
            }
        }
     }
     return 0;
}

linux退出java程序运行窗口 linux退出进程_linux


子进程的PID号为20024

linux退出java程序运行窗口 linux退出进程_linux_02


Z是单词(zombie)的缩写,意思是僵尸

收集子进程退出信息的函数wait和waitpid

头文件

#include <sys/types.h>
#include <sys/wait.h>

函数原型

pid_t wait(int *status);

status:是一个整型数指针
指针为空:不关心退出状态
指针非空:子进程退出状态放在它所指向的地址中

函数作用:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

wait()要与fork()配套出现,如果在使用fork()之前调用wait(),程序出错时wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID.

wait函数的两种参数情况
1、status为NULL时

int main()
{
    int data = 3;
    pid_t pid;
    pid = vfork();
    if(pid > 0){
       wait(NULL);
       while(1){
           printf("Father pid = %d\n",getpid());
           sleep(1);
       }
    }else if(pid == 0){
    	while(1){
        	data--;
            printf("Child pid = %d\n",getpid());
            sleep(3);
            if(data == 0){
            	exit(0);
            }
        }
     }
     return 0;
}

2、status非空时

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int data = 3;
    pid_t pid;
    pid = vfork();
    int status = 10;
    if(pid > 0){
        wait(&status);
        printf("child quit status = %d\n",WEXITSTATUS(status));
        while(1){
            printf("Father pid = %d\n",getpid());
            sleep(1);
        }
    }else if(pid == 0){
     	while(1){
             data--;
             printf("Child pid = %d\n",getpid());
             sleep(3);
             if(data == 0){
                 exit(3);
              }
         }
     }
     return 0;
}

linux退出java程序运行窗口 linux退出进程_嵌入式_03


linux退出java程序运行窗口 linux退出进程_linux_04


可以看到用wait将子进程的返回信息收集了起来,并且将返回的信息放到了status指向的内存中,并且没有出现进程号为20319的僵尸进程,进程被关闭一些宏

linux退出java程序运行窗口 linux退出进程_linux退出java程序运行窗口_05


waitpid

头文件

#include <sys/types.h>
#include <sys/wait.h>

函数原型

pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,wait使调用者阻塞,waitpid 可以使调用者不阻塞

PID:
pid > 0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid = -1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
pid == 0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
pid < -1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

status:是一个整型数指针,和wait一样
options:options提供了一些额外的选项来控制waitpid,目前 在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:

ret = waitpid(-1,  NULL,  WNOHANG | WUNTRACED);

如果我们不想使用它们,也可以把options设为0,如:

ret = waitpid(-1,  NULL,  0);

如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。

返回值:
1、当正常返回的时候,waitpid返回收集到的子进程的进程ID
2、如果设置了选项WNOHANG,在调用中waitpid发现没有已经退出的子进程可收集时,返回0
3、如果调用中出错,则返回-1

例:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int data = 3;
    pid_t pid;
    pid = fork();
    int status = 10;
    if(pid > 0){
        waitpid(pid,&status,WNOHANG);
        printf("child quit status = %d\n",WEXITSTATUS(status));
        while(1){
        	printf("Father pid = %d\n",getpid());
            sleep(1);
        }
    }else if(pid == 0){
        while(1){
            data--;
            printf("Child pid = %d\n",getpid());
            sleep(3);
            if(data == 0){
            	exit(3);
             }
         }
     }
     return 0;
}

linux退出java程序运行窗口 linux退出进程_编程语言_06


linux退出java程序运行窗口 linux退出进程_c语言_07


在这里可以看到进程号为20830的子进程变成了僵尸进程,这是因为用的是fork()函数创建进程,它不会先执行子进程,,再执行父进程,所以在执行父进程的时候waitpid这里没有返回子进程的pid号,又因为设置了选项WNOHANG,所以返回0,当真正的子进程信息返回来的时候,就没有收集,所以变成了僵尸进程。

当把fork改为vfork时,就可以解决

pid = vfork();

linux退出java程序运行窗口 linux退出进程_linux退出java程序运行窗口_08


linux退出java程序运行窗口 linux退出进程_编程语言_09


在这里并没有出现进程号为20917的僵尸进程

三、孤儿进程

父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程
Linux为了避免孤儿进程过多,init进程收留孤儿进程,变成孤儿进程的父进程(init进程为系统初始化进程,进程ID为1)

例如

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int data = 3;
    pid_t pid;
    pid = fork();
    if(pid > 0){
        printf("Father pid = %d\n",getpid());
    }
    else if(pid == 0){
        while(1){
            data--;
            printf("my Father pid = %d Child pid = %d\n",getppid(),getpid());
            sleep(3);
            if(data == 0){
                 exit(3);
             }
         }
     }
     return 0;
}

linux退出java程序运行窗口 linux退出进程_编程语言_10


此时进程号为21311的子进程的父进程就为init了。