进程的终止,回收子进程

2024-08-25 14:04
文章标签 回收 进程 终止

本文主要是介绍进程的终止,回收子进程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

进程的终止

正常终止

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

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

调用exit函数令进程终止 

#include <stdlib.h>
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 <stdio.h>
#include <stdlib.h> //exit函数
#include <unistd.h> //_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 <stdlib.h>
int atexit (void (*function) (void));

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

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

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

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

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

该函数不返回!
#include <stdlib.h>
void _Exit(int status);

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

该函数不返回

代码演示 
//exit函数演示
#include <stdio.h>
#include <stdlib.h> //exit函数
#include <unistd.h> //_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<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
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 <stdlib.h>
void abort(void);

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

 回收子进程

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

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

wait函数

#include <sys/wait.h>
pid_t wait(int* status);

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

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

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

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

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

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

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

代码演示 (单子进程)
//回收子进程
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
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 <stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<errno.h>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 <sys/wait.h>
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 <stdio.h>
#include<unistd.h>
#include<sys/wait.h>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)信号。父进程可以忽略该信号,也可以提供一个针对该信号的信号处理函数,在信号处理函数中以异步的方式回收子进程。这样做不仅流程简单,而且僵尸的存活时间短,回收效率高

这篇关于进程的终止,回收子进程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1105753

相关文章

Windows的CMD窗口如何查看并杀死nginx进程

《Windows的CMD窗口如何查看并杀死nginx进程》:本文主要介绍Windows的CMD窗口如何查看并杀死nginx进程问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录Windows的CMD窗口查看并杀死nginx进程开启nginx查看nginx进程停止nginx服务

Java进程CPU使用率过高排查步骤详细讲解

《Java进程CPU使用率过高排查步骤详细讲解》:本文主要介绍Java进程CPU使用率过高排查的相关资料,针对Java进程CPU使用率高的问题,我们可以遵循以下步骤进行排查和优化,文中通过代码介绍... 目录前言一、初步定位问题1.1 确认进程状态1.2 确定Java进程ID1.3 快速生成线程堆栈二、分析

Python多进程、多线程、协程典型示例解析(最新推荐)

《Python多进程、多线程、协程典型示例解析(最新推荐)》:本文主要介绍Python多进程、多线程、协程典型示例解析(最新推荐),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定... 目录一、multiprocessing(多进程)1. 模块简介2. 案例详解:并行计算平方和3. 实现逻

C#通过进程调用外部应用的实现示例

《C#通过进程调用外部应用的实现示例》本文主要介绍了C#通过进程调用外部应用的实现示例,以WINFORM应用程序为例,在C#应用程序中调用PYTHON程序,具有一定的参考价值,感兴趣的可以了解一下... 目录窗口程序类进程信息类 系统设置类 以WINFORM应用程序为例,在C#应用程序中调用python程序

JVM垃圾回收机制之GC解读

《JVM垃圾回收机制之GC解读》:本文主要介绍JVM垃圾回收机制之GC,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、死亡对象的判断算法1.1 引用计数算法1.2 可达性分析算法二、垃圾回收算法2.1 标记-清除算法2.2 复制算法2.3 标记-整理算法2.4

Python如何精准判断某个进程是否在运行

《Python如何精准判断某个进程是否在运行》这篇文章主要为大家详细介绍了Python如何精准判断某个进程是否在运行,本文为大家整理了3种方法并进行了对比,有需要的小伙伴可以跟随小编一起学习一下... 目录一、为什么需要判断进程是否存在二、方法1:用psutil库(推荐)三、方法2:用os.system调用

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序

Java终止正在运行的线程的三种方法

《Java终止正在运行的线程的三种方法》停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作,停止一个线程可以用Thread.stop()方法,但最好不要用它,本文给大家介绍了... 目录前言1. 停止不了的线程2. 判断线程是否停止状态3. 能停止的线程–异常法4. 在沉睡中停止5

Linux中的进程间通信之匿名管道解读

《Linux中的进程间通信之匿名管道解读》:本文主要介绍Linux中的进程间通信之匿名管道解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、基本概念二、管道1、温故知新2、实现方式3、匿名管道(一)管道中的四种情况(二)管道的特性总结一、基本概念我们知道多

Linux进程终止的N种方式详解

《Linux进程终止的N种方式详解》进程终止是操作系统中,进程的一个重要阶段,他标志着进程生命周期的结束,下面小编为大家整理了一些常见的Linux进程终止方式,大家可以根据需求选择... 目录前言一、进程终止的概念二、进程终止的场景三、进程终止的实现3.1 程序退出码3.2 运行完毕结果正常3.3 运行完毕