进程的终止,回收子进程

进程的终止

正常终止

从main函数中返回可令进程终止

  • main函数一旦返回,主线程即终止,进程即终止,进程一旦终止,进程中的所有线程统统终止。这就是main函数的返回与其它函数的返回在本质上的区别。
  • main函数的返回值即进程的退出码,父进程可以在回收子进程的同时获得该退出码,以了解导致其终止的具体原因。

调用exit函数令进程终止 

#include
void exit(int status);

功能:令进程终止
参数:statusi进程的退出码,相当于main函数的返回值                                                           该函数不返回!!!
        虽然exit函数的参数和main函数的返回值都是int类型,但只有其中最低数位的字节可被其父进程回收,高三个字节会被忽略因此在设计进程的退出码时最好不要超过一字节的值域范围

        通过return语句终止进程只能在main函数中实现,但是调用exit函数终止进程可以在包括 main函数在内的任何函数中使用。
        exit函数在终止调用进程之前还会做几件收尾工作
A、调用实现通过atexit或on exit所数注册的退出处理函数;

B、冲刷并关闭所有仍处于打开状态的标准I/O流;

C、删除所有通过tmpfile函数创建的临时文件;

D、_exit(status);

代码演示 

//exit函数演示
#include 
#include  //exit函数
#include  //_exit函数
//退出处理函数
/*void exitfun(void){
    printf("我是退出处理函数\n");
}
void exitfun2(int status,void *arg){
    printf("status = %d\n",status);
    printf("arg    = %s\n",(char*)arg);
}*/
int fun(void){
    printf("我是fun函数\n");
    exit(0); //终止程序
    //_exit(0); //终止程序
    return 10;
}

int main(){
    //atexit(exitfun); //注册退出处理函数
    //on_exit(exitfun2,"hello"); //注册退出处理函数
    printf("fun函数返回%d\n",fun());
    return 0;
}

注册退出处理函数
#include
int atexit (void (*function) (void));

参数: function 函数指针,指向退出处理函数返回值:成功返回0,失败返回-1
        注意atexit函数本身并不调用退出处理函数,而只是将function参数所表示的退出处理函数地址,保存(注册)在系统内核的某个地方(进程表项)。待到exit函数被调用或在main函数里执行return语句时,再由系统内核根据这些退出处理函数的地址来调用它们。此过程亦称回调

注册退出处理函数
#include
int on_exit (void (*function) (int , void*), void* arg);

参数:function 函数指针,指向退出处理函数。其中第一个参数来自传递给exit函数
        的status参数或在main函数里执行return语句的返回值,而第二个参数则来自传递给on         exit函数的arg参数
        arg泛型指针,将作为第二个参数传递给function所指向的退出处理函数
返回值:成功返回0,失败返回-1

 调用 _exit/_Exit函数令进程终止

#include                                                                                                                   void _exit(int status);
参数:status 进程退出码,相当于main函教的返回值

该函数不返回!
#include
void _Exit(int status);

参数:status 进程退出码,相当于main函数的返回值

该函数不返回

代码演示 

//exit函数演示
#include 
#include  //exit函数
#include  //_exit函数
//退出处理函数
void exitfun(void){
    printf("我是退出处理函数\n");
}
void exitfun2(int status,void *arg){
    printf("status = %d\n",status);
    printf("arg    = %s\n",(char*)arg);
}
int fun(void){
    printf("我是fun函数\n");
    exit(0); //终止程序
    //_exit(0); //终止程序
    return 10;
}

int main(){
    atexit(exitfun); //注册退出处理函数
    on_exit(exitfun2,"hello"); //注册退出处理函数
    printf("fun函数返回%d\n",fun());
    return 0;
}

运行结果 (观察输出结果顺序,栈的特点)

status = 0
arg    = hello
我是退出处理函数

异常终止

1、当进程执行了某些在系统看来具有危险性的操作,或系统本身发生了某种故障或意外,内核会向相关进程发送特定的信号。如果进程无意针对收到的信号采取补救措施,那么内核将按照缺省方式将进程杀死,并视情形生成核心转储文件(core)以备事后分析,俗称吐核
SIGILL(4):进程试图执行非法指令
SIGBUS(7):硬件或对齐错误
SIGFPE(8):浮点异常
SIGSEGV(11):无效内存访问
SIGPWR(30):系统供电不足

代码演示(无效内存访问) 

//回收子进程
#include
#include
#include
int main(){
    //父进程创建子进程
    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }
    //子进程代码,代码暂时不结束
    if(pid == 0){
        printf("%d进程:我是子进程,暂时不结束\n",getpid());
       // sleep(5);
       // return 0;
       //exit(300);
       //_exit(300);
       //_Exit(300);
       //abort();
       int *p = NULL;
       *p = 1;
       return 0;
    }
    // 父进程代码,等待子进程结束收尸
    int status;
    pid_t childpid = wait(&status);
    if(childpid == -1){
        perror("wait");
        return -1;
    }   
    printf("%d进程:回收了%d进程的僵尸\n",getpid(),childpid);
    if(WIFEXITED(status)){
        printf("正常终止:%d\n",WEXITSTATUS(status));
    }else{
        printf("异常终止:%d\n",WTERMSIG(status));
    }
    return 0;
}

运行结果

19055进程:我是子进程,暂时不结束
19041进程:回收了19055进程的僵尸
异常终止:11

2、人为触发信号
SIGINT(2):Ctr+C
SIGQUIT(3):Ctr+\
SIGKILL(9):不能被捕获或忽略的进程终止信号

SIGTERM(15):可以被捕获或忽略的进程终止信号

3、向进程自己发送信号
#include
void abort(void);

功能:向调用进程发送SIGABRT(6)信号,该信号默认情况下可使进程结束无参数,不返回! 

 回收子进程

为什么要回收子进程:
        清除僵尸进程,避免消耗系统资源。

        父进程需要等待子进程的终止,以继续后续工作父进程需要知道子进程终止的原因
        如果是正常终止,那么进程的退出码是多少?
        如果是异常终止,那么进程是被那个信号所终止的?

wait函数

#include
pid_t wait(int* status);

功能:等待并回收任意子进程
参数:status 用于输出子进程的终止状态可置NULL

返回值:成功返回所回收的子进程的PID,失败返回-1 

父进程在创建若干子进程以后调用wait函数:
A.若所有子进程都在运行,则阻塞,直到有子进程终止才返回
B.若有一个子进程已终止,则返回该子进程的PID并通过status参数输出其终止状态

C.若没有任何可被等待并回收的子进程,则,返回-1,置errno为ECHILD。 

 

在任何一个子进程终止前,wait函数只能阻塞调用进程,如果有一个子进程在wait函数被调用之前,已经终止并处于僵尸状态,wait函数会立即返回,并取得该子进程的终止状态,同时子进程僵尸消失。由此可见wait函数主要完成三个任务
1.阻塞父进程的运行,直到子进程终止再继续,停等同步
2.获取子进程的PID和终止状态,令父进程得知谁因何而死
3.为子进程收尸,防止大量僵尸进程耗费系统资源

以上三个任务中,即使前两个与具体需求无关,仅仅第三个也足以凸显wait函数的重要性,尤其是对那些多进程服务器型的应用而言

子进程的终止状态通过wait函数的status参数输出给该函数调用者头文件提供了几个辅助分析进程终止状态的工具宏
WIFEXITED(status)
真:正常终止    WEXITSTATUS(status)进程退出码    
假:异常终止    WTERMSIG(status)-> 终止进程的信号    
WIFSIGNALED(status)
真:异常终止    WTERMSIG(status) -> 终止进程的信号    
假:正常终止    WEXITSTATUS(status) ->进程退出码    

代码演示 (单子进程)

//回收子进程
#include
#include
#include
int main(){
    //父进程创建子进程
    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }
    //子进程代码,代码暂时不结束
    if(pid == 0){
        printf("%d进程:我是子进程,暂时不结束\n",getpid());
       // sleep(5);
       // return 0;
       //exit(300);
       //_exit(300);
       //_Exit(300);
       //abort();
       int *p = NULL;
       *p = 1;
       return 0;
    }
    // 父进程代码,等待子进程结束收尸
    int status;
    pid_t childpid = wait(&status);
    if(childpid == -1){
        perror("wait");
        return -1;
    }   
    printf("%d进程:回收了%d进程的僵尸\n",getpid(),childpid);
    if(WIFEXITED(status)){
        printf("正常终止:%d\n",WEXITSTATUS(status));
    }else{
        printf("异常终止:%d\n",WTERMSIG(status));
    }
    return 0;
}

代码演示(多子进程) 

//回收多个子进程
#include 
#include
#include
#include

int main(){
    //创建多个子进程
    for(int i = 0; i < 5; i++){
        pid_t pid = fork();
        if(pid == -1){
            perror("fork");
            return -1;
        }
        if(pid == 0){
            printf("%d进程:我是子进程\n", getpid());
            sleep(1 + i);
            return 0;
        }
    }
    for(;;){
        pid_t pid = wait(NULL);  //NULL不保存子进程的终止状态
        if(pid == -1){
            if(errno == ECHILD){
                printf("没有子进程\n");
                break;
            }else{
                perror("wait");
                return -1;
            }
        }
        printf("%d进程:回收了%d进程\n",getpid(),pid);
    }
    return 0;
}

waitpid函数 

 #include
pid_t waitpid(pid_t pid, int* status, int options);

>功能:等待并回收任意或特定子进程
参数:pid 可取以下值
        -1等待并回收任意子进程相当于wait函数
       >0 等待并回收特定子进程
        status 用于输出子进程的终止状态,可置NULL

        options可以如下取值
                0  阻塞模式,若所等子进程仍在运行,则阻塞直至其终止

                WNOHANG  非阻塞模式,若所等子进程仍在运行,则返回 0

返回值:成功返回所回收子进程的PID或者0,失败返回-1。
waitpid(-1,&status,0)等价于 wait(&status)

代码演示 

// 非阻塞回收
#include 
#include
#include

int main(){
    //创建子进程
        pid_t pid = fork();
        if(pid == -1){
            perror("fork");
            return -1;
        }
        //子进程代码,暂时不结束
        if(pid == 0){
            printf("%d进程:我是子进程\n",getpid());
            sleep(10);
            return 0;
        }
        //父进程代码,以非阻塞方式回收子进程
    while(1){
        pid_t childpid = waitpid(pid,NULL,WNOHANG);
        if(childpid == -1){
            perror("waitpid");
            return -1;
        }else if(childpid == 0){
            printf("%d进程:子进程还在运行,无法回收\n",getpid());
            sleep(1);
        }else{
            printf("%d进程:回收了%d进程的僵尸\n",getpid(),childpid);
            break;
        }
    }
    return 0;

}

运行结果 

22618进程:我是子进程
22613进程:子进程还在运行,无法回收
22613进程:子进程还在运行,无法回收
22613进程:子进程还在运行,无法回收
22613进程:子进程还在运行,无法回收
22613进程:子进程还在运行,无法回收
22613进程:子进程还在运行,无法回收
22613进程:子进程还在运行,无法回收
22613进程:子进程还在运行,无法回收
22613进程:子进程还在运行,无法回收
22613进程:子进程还在运行,无法回收
22613进程:回收了22618进程的僵尸

        事实上,无论一个进程是正常终止还是异常终止,都会通过系统内核向其父进程发送SIGCHLD(17)信号。父进程可以忽略该信号,也可以提供一个针对该信号的信号处理函数,在信号处理函数中以异步的方式回收子进程。这样做不仅流程简单,而且僵尸的存活时间短,回收效率高

 

 

你可能感兴趣的:(linux,vim,开发语言,c语言,vscode,ubuntu)