【Linux探索学习】第十七弹——进程终止:深入解析操作系统中的进程终止机制

Linux学习笔记:

https://blog.csdn.net/2301_80220607/category_12805278.html?spm=1001.2014.3001.5482

前言:

在操作系统中,进程终止是一个至关重要的阶段,它标志着进程的生命周期结束。进程终止可能是因为任务完成,也可能是因为异常或外部干预。本文将详细讲解操作系统中的进程终止相关知识,包括终止的原因、类型、实现方式、Linux系统中的具体操作,以及其影响和管理策略,并配以表格和代码示例,帮助全面掌握这一主题。


目录

一、什么是进程终止?

二、进程终止的主要原因

三、进程终止的类型

四、Linux中的进程终止实现

[4.1 运行完毕且正常终止](#4.1 运行完毕且正常终止)

[4.1.1 使用return终止进程](#4.1.1 使用return终止进程)

[4.1.2 使用exit终止进程](#4.1.2 使用exit终止进程)

[4.2 errno常量和strerror函数](#4.2 errno常量和strerror函数)

[4.2.1 strerror函数](#4.2.1 strerror函数)

[4.2.2 errno常量](#4.2.2 errno常量)

[4.3 异常终止:abort](#4.3 异常终止:abort)

[4.4 强制终止:kill](#4.4 强制终止:kill)

[4.4 子进程资源回收:wait 和 waitpid](#4.4 子进程资源回收:wait 和 waitpid)

五、进程终止的影响

[5.1 资源释放](#5.1 资源释放)

[5.2 僵尸进程](#5.2 僵尸进程)

如何避免僵尸进程?

六、信号与进程终止

常见信号与作用

示例代码:捕获SIGTERM信号

七、进程终止的常见问题与解决

[7.1 僵尸进程问题](#7.1 僵尸进程问题)

[7.2 非预期终止](#7.2 非预期终止)

八、总结


一、什么是进程终止?

**进程终止(Process Termination)**是操作系统中进程生命周期的最后一个阶段,意味着操作系统回收该进程的所有资源,包括内存、文件描述符、CPU时间等,使这些资源可以被其他进程使用。


二、进程终止的主要原因

进程可能因多种原因终止:

终止原因 描述
正常终止 进程完成所有任务后自然结束,例如程序执行到return语句或调用exit函数。
异常终止 由于未处理的错误或异常导致进程终止,例如除以零、非法访问内存等。
外部干预 进程被操作系统或其他进程强制终止,例如接收到SIGKILL信号。
父进程终止 当父进程终止且子进程未被接管时,子进程可能成为孤儿进程,由initsystemd进程接管。
资源耗尽 进程因超出系统资源限制(如内存、文件句柄等)被操作系统强制终止。

三、进程终止的类型

进程终止根据触发方式可以分为以下几类:

类型 触发方式 常见场景
正常终止 调用exit()、返回主函数 程序完成任务后自然结束。
异常终止 未处理的错误或调用abort() 例如访问非法地址、未处理的信号等。
强制终止 外部进程调用kill()、操作系统干预 父进程发送SIGKILL信号或管理员手动终止进程。
核心转储终止 错误导致生成核心转储文件 例如段错误(SIGSEGV)导致的异常。

一般进程终止的场景包含一下三种:

  1. 代码运行完毕,结果正常

  2. 代码运行完毕,结果不正常

  3. 代码异常终止

下面我们会对上面的内容做出讲解


四、Linux中的进程终止实现

在Linux操作系统中,进程终止主要通过以下系统调用和信号实现:

4.1 运行完毕且正常终止

4.1.1 使用return终止进程

我们平时用的最多的方式就是return,我们先来看下面一个简单的代码

cpp 复制代码
  #include<stdio.h>                    
     
  int main()   
  {   
      printf("This is a test message");
      return 0;
  }        

我们平时所写的代码main函数中一般都有一个返回值,那么这个返回值是干什么的呢?

main函数返回值是返回给进程看的,本质表示:进程运行完成时是否是正确的结果,如果是一般返回0,如果不是返回其它数字代表不同的退出信息(退出码)

我们可以通过这个指令打印退出码:

cpp 复制代码
echo $?
4.1.2 使用exit终止进程

exit系统调用用于正常终止进程,并返回一个状态码给操作系统或父进程。

我们使用exit一般是在进程正常终止但没有正常执行的场景,或者是在合适的地方进行截停的场景,我们来看下面一段代码:

cpp 复制代码
#include<stdio.h> 
#include<stdlib.h>                                                                                                                                                                                           
void print()      
{                 
    printf("hello linux\n");
    printf("hello linux\n");
    printf("hello linux\n");
    printf("hello linux\n");
    exit(20);     
    printf("hello linux\n");
    printf("hello linux\n");
}                 
int main()        
{                 
    print();      
    return 10;    
}                 

我们来看一下上面内容的执行结果和返回值:

我们发现返回值是exit中的返回值,并不是return的返回值,而且打印也只执行了四行,所以我们可以知道带有exit的进程,在执行到它时会直接返回,并不会再继续执行后面的内容,返回值也返回exit的返回值,这一点与return有较大差别


4.2 errno常量和strerror函数

4.2.1 strerror函数

上面我们提到我们可以通过不同的退出码来代表不同的错误信息,那么不同的退出码究竟各自代表什么信息呢?我们可以通过strerror函数来查看

比如我们来看一下退出码0到10所代表的信息:

cpp 复制代码
#include<stdio.h>
#include<string.h>
int main()
{
    for(int i=0;i<=10;i++)
    {
        printf("%d: %s\n",i,strerror(i));
    }
    return 0;
}                

运行结果:

4.2.2 errno常量

上面我们讲到进程在退出是会有退出码,我们可以通过echo来查看退出码,那我们如何获取呢?

C/C++中其实还定义了一个叫errno的常量来记录错误码

所以我们就可以将errno常量与strerror函数结合使用,用errno来记录进程的错误码,然后传给strerror函数得到错误信息,比如下面的例子:

cpp 复制代码
#include<stdio.h> 
#include<unistd.h>                                                                                                                                                                                           
#include<string.h>
#include<stdlib.h>
#include<errno.h>     //注意要带好头文件
int main()        
{                 
    int ret=0;    
    char *p=(char*)malloc(1000*1000*1000*4);    //这个扩容肯定会出错的,因为扩容空间太大了
    if(p==NULL)   
    {             
        printf("mallo error, %d:%s\n",errno,strerror(errno));   //errno会记录错误码,将它传到strerror中就可以得到错误信息
        ret=errno;    //将错误码作为返回值返回,从而让父进程得到返回信息
    }             
    else          
    {             
        printf("malloc success\n");
    }             
    return ret;   
}                 

运行结果:


4.3 异常终止:abort

4.2和4.3都牵扯到了信号的内容,这里我们主要还是以了解为主,后期我们会详细讲解信号的知识

abort函数用于非正常终止进程,通常在遇到不可恢复的错误时调用。

示例代码:abort

cpp 复制代码
#include <stdlib.h>
#include <stdio.h>

int main() {
    printf("遇到严重错误,程序终止!\n");
    abort();  // 异常终止
    return 0;  // 不会被执行
}

调用abort会产生一个信号(SIGABRT),通常会生成一个核心转储文件供调试使用。


4.4 强制终止:kill

kill系统调用或命令用于向目标进程发送信号,例如SIGKILL信号会立即强制终止目标进程。

示例代码:kill

cpp 复制代码
#include <signal.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    pid_t pid = fork();

    if (pid == 0) {
        // 子进程
        while (1) {
            printf("子进程正在运行: PID = %d\n", getpid());
            sleep(1);
        }
    } else if (pid > 0) {
        // 父进程
        sleep(5);
        printf("终止子进程: PID = %d\n", pid);
        kill(pid, SIGKILL);  // 发送SIGKILL信号
    }

    return 0;
}

执行结果:

我们发现子进程在被执行了五次后被终止掉了,对应的就是代码中执行了五次后会执行kill指令杀死进程


4.4 子进程资源回收:waitwaitpid

进程等待与回收我们会在下一篇详细讲解

当子进程终止后,其状态会保留在系统中,直到父进程回收。这种未被回收的子进程称为僵尸进程

示例代码:回收子进程资源

cpp 复制代码
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();

    if (pid == 0) {
        // 子进程
        printf("子进程开始: PID = %d\n", getpid());
        sleep(2);
        printf("子进程结束\n");
    } else if (pid > 0) {
        // 父进程
        int status;
        wait(&status);  // 等待子进程终止
        if (WIFEXITED(status)) {
            printf("子进程正常终止,状态码: %d\n", WEXITSTATUS(status));
        }
    }

    return 0;
}

执行结果:


五、进程终止的影响

5.1 资源释放

进程终止后,操作系统会回收以下资源:

  • 内存:代码段、数据段、堆栈等。
  • 文件描述符:关闭该进程打开的所有文件。
  • CPU时间:释放进程的时间片。

5.2 僵尸进程

当子进程终止但父进程未调用waitwaitpid回收其状态时,子进程会变成僵尸进程

如何避免僵尸进程?
  1. 父进程调用waitwaitpid回收子进程。
  2. 使用信号处理机制,如捕获SIGCHLD信号。

六、信号与进程终止

常见信号与作用

信号 描述 默认行为
SIGKILL 强制终止进程,无法捕获或忽略。 终止
SIGTERM 请求终止进程,进程可以选择捕获或忽略。 终止
SIGABRT 异常终止进程,通常由abort触发。 终止并生成核心转储
SIGCHLD 子进程终止或停止时通知父进程。 忽略
示例代码:捕获SIGTERM信号
cpp 复制代码
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void handle_sigterm(int sig) {
    printf("接收到SIGTERM信号,进程终止\n");
    exit(0);
}

int main() {
    signal(SIGTERM, handle_sigterm);  // 注册信号处理函数

    while (1) {
        printf("程序正在运行...\n");
        sleep(2);
    }

    return 0;
}

七、进程终止的常见问题与解决

7.1 僵尸进程问题

问题:父进程未回收子进程,导致系统中出现僵尸进程。

解决

  • 调用waitwaitpid
  • 使用SIGCHLD信号处理函数自动回收。

7.2 非预期终止

问题:进程意外终止导致数据未保存。

解决:通过信号处理函数捕获终止信号,并在终止前完成必要的清理工作。


八、总结

进程终止是操作系统中管理资源的重要环节。通过本文的讲解,我们了解了进程终止的主要原因、类型以及Linux中的具体实现方式。进程终止不仅影响单个进程的生命周期,还对系统资源的利用和稳定性产生重要影响。

通过合理地使用exitkillwait等系统调用,以及信号机制,可以高效管理进程终止,避免僵尸进程问题,提高系统性能和可靠性。希望通过本文的详细分析和代码示例,你能更加深入理解操作系统的这一重要机制!

本篇笔记:


感谢各位大佬观看,创作不易,还请各位大佬点赞支持!!!

相关推荐
皓月盈江2 分钟前
wordpress网站首页底部栏显示网站备案信息
linux·服务器·wordpress·网站备案·footer.php·主题编辑器·网站底部添加备案信息
花姐夫Jun8 分钟前
node.js基础学习-cheerio模块-简单小爬虫(五)
爬虫·学习·node.js·网络爬虫
Xlous_Mao12 分钟前
# issue 6 网络编程基础
服务器·网络·学习·issue
花姐夫Jun14 分钟前
node.js基础学习-fs模块-文件操作(六)
学习·node.js·文件操作·fs
算法黑哥19 分钟前
服务器挖矿
运维·服务器
erxij26 分钟前
【游戏引擎之路】登神长阶(十五)——DirectX12龙书:行百里者半九十(学习阶段完结)
c++·经验分享·学习·游戏·3d·游戏引擎
洪大宇43 分钟前
开发一套ERP 第八弹 RUst 插入数据
linux
想和我重名?1 小时前
Linux下的wlan0控制
linux·嵌入式
美式小田1 小时前
ADS学习笔记 7. 超外差接收机设计
笔记·学习·射频工程
LeonNo111 小时前
Linux:systemd进程管理【1】
linux·运维·服务器