当前位置: 首页 > news >正文

进程终止机制详解:退出场景、退出码与退出方式全解析

目录

一、进程退出场景

二、进程退出码

1、什么是进程退出码?

2、如何查看退出码?

3、为什么用0表示代码执行成功,而非0表示错误?

4、退出码的含义

5、常见Linux Shell退出码

总结表

注意事项

6、进程退出码的作用

1. 反馈执行结果

2. 错误诊断

3. 脚本自动化控制

4. 进程间协作

5. 约定俗成的标准

6. 调试与日志

7、退出码的存储与意义

三、进程常见退出方式

1、进程正常退出

1. return

2. exit(标准C库函数)

3. _exit(系统调用)

关键区别总结

联系与使用场景

总结

4. return、exit 和 _exit 的区别与联系

return、exit和_exit之间的区别:

缓冲区的归属问题

return、exit和_exit之间的联系

2、进程异常退出

情况一:信号触发导致的进程异常退出

情况二:代码缺陷引发的进程异常退出


进程终止的本质是释放系统资源,包括释放进程申请的内核数据结构以及对应的数据和代码。

一、进程退出场景

进程退出只有三种情况:

  1. 代码运行完毕,结果正确。
  2. 代码运行完毕,结果不正确。
  3. 代码异常终止(进程崩溃)。

二、进程退出码

        程序执行时,main函数作为用户代码的入口,实际上是通过系统调用链被间接调用的。例如在VS2013环境中,main函数由__tmainCRTStartup函数调用,而后者又通过系统加载器被操作系统调用。

        由于main函数最终由操作系统调用,它需要通过返回值向系统传递程序执行状态。通常约定返回0表示程序正常结束,非零值表示异常终止。这就是我们习惯在main函数末尾返回0的原因。

        程序运行时转化为进程,main函数的返回值就成为该进程的退出码。在Linux系统中,可以通过echo $?命令查看最近执行的进程退出码,方便调试和错误排查。

1、什么是进程退出码?

        进程退出码(Exit Code,也称为退出状态)是一个数字值,表示一个程序或命令执行完成后返回给操作系统或父进程的状态信息。它告诉我们这个程序是成功执行还是遇到了错误。

2、如何查看退出码?

在Linux中,可以使用特殊变量 $? 来查看上一个命令的退出码:

# 执行一个命令
ls# 查看退出码
echo $?

        退出码(退出状态)表示最后一次执行的命令状态。当命令结束后,我们可以通过退出码判断命令是否成功执行。

  • 0表示执行成功
  • 1或其他非零值表示执行失败

例如,对于下面这个简单的代码: 

#include<stdio.h>int main()
{printf("hello!");return 0;
}

代码运行结束后,我们可以查看该进程的进程退出码: 

echo $?

此时可以确认main函数已顺利执行完毕。

3、为什么用0表示代码执行成功,而非0表示错误?

        因为程序执行成功只有一种状态,而失败却可能由多种原因引起,比如内存不足、非法访问或栈溢出等。通过不同的非零值,我们可以区分各种错误类型。

C语言中的strerror函数能够根据错误码返回对应的错误信息描述:

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

运行代码后我们就可以看到各个错误码所对应的错误信息: 

        在Linux系统中,ls、pwd等命令本质上都是可执行程序。我们可以通过检查这些命令的退出码来验证其执行状态。正如预期的那样,当这些命令成功执行时,其退出码都会返回0。

当命令执行失败时,系统会返回非零的退出码,这个数字对应着特定的错误信息:

注意:

        每个退出码都对应特定的错误信息,便于用户定位执行失败的原因。这些退出码的具体含义是人为定义的,在不同环境下,相同的退出码可能代表不同的错误信息。

4、退出码的含义

退出码通常有以下约定:

  • 0:表示成功执行,没有错误

  • 1-255:表示执行过程中出现了某种错误

5、常见Linux Shell退出码

退出码解释
0命令成功执行
1通用错误代码
2命令(或参数)使用不当
126权限被拒绝(或)无法执行
127未找到命令,或 PATH 错误
128+n命令被信号从外部终止,或遇到致命错误
130通过 Ctrl+C 或 SIGINT 终止(终止代码 2 或键盘中断)
143通过 SIGTERM 终止(默认终止)
255/*退出码超过了 0-255 的范围,因此重新计算(LCTT 译注:超过 255 后,用退出取模)
  1. 退出码 0

    • 含义:命令执行成功,无错误。

    • 示例:正常完成的命令。

  2. 退出码 1

    • 含义:一般性错误,通常表示“不被允许的操作”或常见错误。

    • 示例

      • 在没有 sudo 权限的情况下使用 yum

      • 除以零的操作(如 let a=1/0)。

  3. 退出码 130 (SIGINT)

    • 含义:进程被中断(通常是用户按下了 Ctrl+C)。

    • 说明:属于 128 + n 信号,其中 n 是终止信号编号(SIGINT 的信号编号为 2,因此 128 + 2 = 130)。

  4. 退出码 143 (SIGTERM)

    • 含义:进程被终止(通常由系统或管理员发送的终止信号)。

    • 说明:属于 128 + n 信号(SIGTERM 的信号编号为 15,因此 128 + 15 = 143)。

  5. 其他说明

    • 128 + n 信号:许多退出码是终止信号加上 128 的结果,其中 n 是信号编号。

    • strerror 函数:可用于获取退出码对应的描述信息。

总结表

退出码含义典型场景信号类型
0成功命令正常执行完成
1一般错误权限不足、除以零等
130进程被中断用户按下 Ctrl+C (SIGINT)128 + 2
143进程被终止系统发送终止信号 (SIGTERM)128 + 15

注意事项

  • 其他退出码可能遵循 128 + n 规则,具体取决于信号类型。

  • 使用 strerror 可以进一步诊断退出码的具体原因。

6、进程退出码的作用

        进程退出码(Exit Code)是进程终止时返回给操作系统的一个整数值,用于表示进程的结束状态。其核心作用如下:

1. 反馈执行结果

  • 成功(通常为 0:表示程序正常执行完毕,无错误。

  • 非零值:表示异常终止或错误(具体含义由程序定义)。

2. 错误诊断

  • 通过非零退出码区分错误类型(例如:1 表示通用错误,2 表示参数错误等)。

  • 帮助脚本或调用者定位问题根源。

3. 脚本自动化控制

  • 在Shell脚本中通过 $? 获取退出码,决定后续逻辑(如重试、告警或终止流程)。

  • 示例:

    some_command
    if [ $? -ne 0 ]; thenecho "命令执行失败!"
    fi

4. 进程间协作

父进程(如监控工具、调度系统)通过子进程的退出码判断其状态并采取相应动作。

5. 约定俗成的标准

  • 常见约定

    • 0:成功(POSIX标准)。

    • 1-127:程序自定义错误(部分系统保留 >128 的信号终止码)。

    • 126:权限不足,127:命令未找到(Shell常用)。

6. 调试与日志

非零退出码可触发日志记录或告警,辅助运维人员快速排查问题。

7、退出码的存储与意义

  • 存储位置

    退出码保存在进程的 task_struct(Linux内核中描述进程的结构体)中,供父进程(如Shell)通过 wait() 系统调用读取。
  • 异常情况下的退出码

    若进程因信号终止(如段错误 SIGSEGV),退出码通常无意义,父进程通过信号编号判断异常原因。

三、进程常见退出方式

1、进程正常退出

1. return

  • 作用范围:用于函数内,返回控制权给调用者。

  • 行为

    • main()函数中,return n 等价于调用 exit(n),会触发程序终止并执行清理(如刷新缓冲区、调用atexit注册的函数等)。

    • 在其他函数中,仅退出当前函数栈帧(后面会补充上函数栈帧的博客)

  • 示例

    int func() {return 1; // 退出当前函数,返回1给调用者
    }
    int main() {return 0; // 退出程序,状态码0,执行清理
    }
    #include<stdio.h>int main()
    {printf("hello!");return 0;
    }

2. exit(标准C库函数)

        调用exit函数是终止进程的常用方法,它可以在程序任意位置退出进程。exit 函数最终会调用 _exit,但在结束进程前会进行以下清理工作:

  1. 执行通过atexiton_exit注册的清理函数
  2. 关闭所有已打开的流,并确保缓冲区数据完成写入
  3. 最终调用_exit函数终止进程
  • 头文件#include <stdlib.h>

  • 功能

    • 终止进程,返回状态码status(0表示成功,非0表示错误)。

    • 会刷新缓冲区、关闭文件流、调用atexit注册的函数。

  • 行为

    • 正常终止进程,状态码传递给父进程(通过wait获取)。

    • 执行清理:刷新缓冲区(如printf未输出的内容)、关闭文件描述符、调用atexit注册的函数。

  • 示例

    exit会在终止前将缓冲区的数据输出:

    #include<stdio.h>  
    #include<stdlib.h>int main() 
    {printf("hello");exit(0);
    }
    

    运行结果:

3. _exit(系统调用)

  • 头文件#include <unistd.h>

  • 参数status 定义进程的终止状态,父进程可通过 wait 获取该值。
  • 说明:虽然 statusint 类型,但仅有低 8 位会传递给父进程。例如 _exit(-1) 执行后,在终端查询 $? 会显示返回值 255。
  • 功能

    • 立即终止进程,不刷新缓冲区、不调用atexit函数。

    • 状态码status传递给父进程。

  • 行为

    • 立即终止进程,不执行任何清理:

      • 不刷新缓冲区(如printf内容可能丢失)。

      • 不调用atexit注册的函数。

    • 直接通过Linux内核终止进程。

  • 典型用途:在子进程fork()后,避免干扰父进程的I/O缓冲区。

  • 示例

    #include<stdio.h>  
    #include<unistd.h>int main() 
    {printf("hello");_exit(0);
    }
    

关键区别总结

特性return (在main)exit_exit
是否刷新缓冲区
是否调用atexit函数
依赖头文件-<stdlib.h><unistd.h>
终止层次函数/程序整个进程整个进程

联系与使用场景

  • exit vs _exit

    • 多数情况下使用exit,确保资源正确释放。

    • fork()后的子进程中,若需立即退出且不干扰父进程,用_exit

    • 例如,子进程执行exec失败时,应调用_exit避免重复清理父进程的资源。

  • return vs exit

    • main()中,returnexit效果相同。

    • 在非main函数中,return仅退出当前函数,而exit会终止整个进程。

总结

  • 程序正常退出:优先用exitmain中的return

  • 子进程或紧急退出:用_exit避免副作用。

  • 理解缓冲区与清理机制的差异是避免资源泄漏的关键。

    4. return、exit 和 _exit 的区别与联系

    return、exit和_exit之间的区别:

    1. 作用范围不同:

      • 仅在 main 函数中的 return 能退出进程
      • 子函数中的 return 不会终止进程
      • exit 和 _exit 在任何位置调用均可终止进程
    2. 终止行为不同:

      • exit 会在终止前执行:
        • 用户定义的清理函数
        • 库缓冲区刷新操作(如 stdio 的缓冲数据
        • 关闭文件流等收尾工作
      • _exit 则直接终止进程,不刷新缓冲区(可能导致未输出的数据丢失),不做任何清理操作。

    缓冲区的归属问题

    • 这里的缓冲区是C标准库(如 stdio.h)提供的缓冲区,而非操作系统内核内部的缓冲区。

    • 例如 printf 的数据会先存入库缓冲区,调用 exit() 或遇到 \n 时才会刷新到内核缓冲区。

    • 注意_exit() 绕过库缓冲区,直接终止进程,可能导致未刷新数据丢失。

    return、exit和_exit之间的联系

            调用main函数结束时会将其返回值作为参数传递给exit函数,因此执行return num与直接调用exit(num)效果相同。

            使用exit函数退出进程前,exit函数会先执行用户定义的清理函数、冲刷缓冲,关闭流等操作,然后再调用_exit函数终止进程。

    2、进程异常退出

    情况一:信号触发导致的进程异常退出

    例如,在进程运行期间发送kill -9信号强制终止进程,或使用Ctrl+C中断进程运行等场景。

    情况二:代码缺陷引发的进程异常退出

    例如,程序存在野指针访问导致崩溃,或出现除以零等算术异常致使进程异常终止等情况。

    http://www.dtcms.com/a/283763.html

    相关文章:

  1. Django中get()与filter()对比
  2. 3D材质总监的“光影魔法”:用Substance Sampler AI,“擦除”照片中的光影
  3. 3D Gaussian Splatting (3DGS) 从入门到精通:安装、训练与常见问题全解析
  4. 如何构建一个基于大模型的实时对话3D数字人?
  5. 【代码随想录】+ leetcode hot100:栈与队列算法专题总结、单调栈
  6. 【leetcode】852. 山脉数组的封顶索引
  7. MySQL数据库主从复制
  8. 如何将 ONLYOFFICE 文档集成到使用 Laravel 框架编写的 PHP 网络应用程序中
  9. 7.事务操作
  10. 第2章通用的高并发架构设计——2.6 高并发写场景方案1:数据分片之数据库分库分表
  11. win10 安装mysql启动
  12. 配置mysql
  13. ONLYOFFICE Docs 9.0 重磅上线:全面升级界面体验,AI 驱动高效办公
  14. Java全栈工程师面试实录:从电商支付到AI大模型架构的深度技术挑战
  15. 下载了docker但是VirtualBox突然启动不了了
  16. [IRF/Stack]华为/新华三交换机堆叠配置
  17. 安装wsl-Ubuntu到D盘
  18. 虚拟化测试工具Parasoft Virtualize如何为汽车企业提供仿真测试?
  19. php主流框架FastAdmin框架详解以及如何查看版本号和初始安装fastadmin框架-优雅草卓伊凡|大东家
  20. Java并发编程第三篇(深入解析Synchronized)
  21. Python reduce函数和lambda表达式完全指南 | 函数式编程教程
  22. Day04_C语言网络编程20250716_sql语言大全
  23. API 接口开发与接入实践:自动化采集淘宝商品数据
  24. 基于单片机公交车报站系统/报站器
  25. 国产化PDF处理控件Spire.PDF教程:使用 Python 向 PDF 添加文字(支持创建与编辑)
  26. 腾讯位置商业授权鸿蒙地图SDK工程配置
  27. 网络爬虫的详细知识点
  28. 【JVM】深入理解 JVM 类加载器
  29. 语雀编辑器内双击回车插入当前时间js脚本
  30. Webpack5 新特性与详细配置指南