进程终止:exit()与_exit()深度解析
在 Linux 系统编程中,进程的终止 是一个非常关键的概念。无论是正常退出还是异常退出,都需要合理地释放资源并通知父进程。
本节我们将详细讲解:
exit()
与_exit()
的区别- 进程终止的方式
- 终止状态码的作用
- 内核如何清理进程资源
目录
一、进程终止的几种方式
二、exit() 函数
1. 功能:
2. 函数原型:
3. 参数说明:
4. 主要行为:
三、_exit() 函数
1. 功能:
2. 函数原型:
3. 参数说明:
4. 主要行为:
四、main 函数返回值等价于 exit()
五、exit() vs _exit()
六、退出状态码(Exit Status)
示例:查看上一个命令的退出状态(Shell 中)
示例代码:获取子进程退出状态
七、进程终止流程图解(知识树状图)
八、atexit() 函数 —— 注册退出处理函数
示例代码:
九、总结知识点图解(知识树状图)
十、课后练习建议
一、进程终止的几种方式
一个进程可以通过以下方式终止:
方式 | 描述 |
---|---|
正常终止 | 调用 exit() 或 _exit() ,或从 main 函数返回 |
异常终止 | 收到某些信号(如 SIGABRT、SIGSEGV) |
父进程回收 | 使用 wait() 或 waitpid() 获取子进程退出状态 |
二、exit() 函数
1. 功能:
exit()
是标准 C 库函数,用于正常终止当前进程,并在退出前执行一些清理操作。
2. 函数原型:
#include <stdlib.h>
void exit(int status);
3. 参数说明:
status
:退出状态码,通常为 0 表示成功,非零表示错误。- 建议使用
EXIT_SUCCESS
和EXIT_FAILURE
宏定义。
- 建议使用
4. 主要行为:
- 执行注册的
atexit()
函数(如关闭文件、释放资源) - 刷新标准 I/O 缓冲区(如 stdout)
- 关闭所有打开的流
- 将控制权交给内核,进入“僵尸”状态,等待父进程回收
三、_exit() 函数
1. 功能:
_exit()
是系统调用,直接终止进程,不进行任何清理操作。
2. 函数原型:
#include <unistd.h>
void _exit(int status);
3. 参数说明:
status
:退出状态码,同上。
4. 主要行为:
- 不刷新缓冲区
- 不执行 atexit 注册的函数
- 不关闭文件描述符(除非设置了 close-on-exec 标志)
- 直接将控制权交给内核
适用场景: 在
vfork()
创建的子进程中必须使用_exit()
,否则会影响父进程数据。
四、main 函数返回值等价于 exit()
在 main 函数中返回整数,其效果等同于调用 exit()
:
int main() {return 0;
}
等价于:
int main() {exit(0);
}
五、exit() vs _exit()
特性 | exit() | _exit() |
---|---|---|
属于哪个库 | stdlib.h | unistd.h |
是否刷新缓冲区 | 是 | 否 |
是否执行 atexit 函数 | 是 | 否 |
是否关闭流 | 是 | 否 |
是否安全用于 vfork 子进程 | 否 | 是 |
用途 | 正常退出,确保清理 | 快速退出,避免副作用 |
六、退出状态码(Exit Status)
每个进程终止时都会返回一个状态码给父进程,范围是 0~255。
- 0:表示成功
- 非零:表示错误(不同数字可代表不同的错误类型)
示例:查看上一个命令的退出状态(Shell 中)
ls /tmp
echo $? # 输出 ls 的退出状态
示例代码:获取子进程退出状态
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main() {pid_t pid = fork();if (pid == 0) {// 子进程printf("Child: exiting with code 3\n");exit(3); // 或 _exit(3)} else if (pid > 0) {int status;wait(&status);if (WIFEXITED(status)) {printf("Parent: Child exited with status %d\n", WEXITSTATUS(status));}}return 0;
}
输出示例:
Child: exiting with code 3
Parent: Child exited with status 3
七、进程终止流程图解(知识树状图)
+-----------------------------+
| 当前进程 |
+-----------------------------+|v
+-----------------------------+
| exit() 或 _exit() 被调用 |
+-----------------------------+|v
+-----------------------------+
| exit(): 清理缓冲区、调用 atexit |
| _exit(): 直接终止,无清理 |
+-----------------------------+|v
+-----------------------------+
| 进程进入僵尸状态(Zombie) |
| 等待父进程调用 wait() 回收 |
+-----------------------------+
八、atexit() 函数 —— 注册退出处理函数
可以使用 atexit()
注册多个函数,在 exit()
被调用时按后进先出顺序执行。
示例代码:
#include <stdio.h>
#include <stdlib.h>void handler1() {printf("Handler 1 called\n");
}void handler2() {printf("Handler 2 called\n");
}int main() {atexit(handler1);atexit(handler2);printf("Main function returning\n");return 0;
}
输出结果:
Main function returning
Handler 2 called
Handler 1 called
九、总结知识点图解(知识树状图)
进程终止(exit、_exit)
│
├── 进程终止方式
│ ├── 正常退出(exit(), _exit(), main 返回)
│ └── 异常退出(收到信号)
│
├── exit()
│ ├── 属于 stdlib.h
│ ├── 刷新缓冲区
│ ├── 执行 atexit 注册的函数
│ └── 推荐用于正常退出
│
├── _exit()
│ ├── 属于 unistd.h
│ ├── 不刷新缓冲区
│ ├── 不执行 atexit 函数
│ └── 适用于 vfork 子进程
│
├── 退出状态码
│ ├── 0 表示成功
│ ├── 非零表示错误
│ └── 父进程通过 wait() 获取
│
├── wait() / waitpid()
│ ├── 回收子进程资源
│ └── 获取退出状态
│
└── atexit()├── 注册退出处理函数└── LIFO 顺序调用
十、课后练习建议
- 编写程序,比较
exit()
与_exit()
对缓冲区的影响(例如输出未换行的字符串)。 - 使用
vfork()
创建子进程,并在子进程中调用exit()
与_exit()
,观察对父进程的影响。 - 在 Shell 中运行一个脚本,故意让它失败,然后用
$?
查看退出状态码。 - 编写程序注册多个
atexit()
处理函数,验证它们的执行顺序。 - 使用
strace
分析exit()
和_exit()
的系统调用差异。
strace -f ./your_program