Linux 进程终止
一个进程退出一般分为以下这三种情况:
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
一个进程退出后会有一个退出码,该退出码是给父进程看的,子进程由父进程创建的,父进程自然是需要关心子进程的情况的,也正是因此,bash 或其他 shell 作为处理命令
的进程,我们在命令行中运行的所有可执行程序都是可以被 shell 给拿到的,也正因此,shell 提供了 “echo $?” 指令用于获取最近一次进程的退出码
我们直接写一个程序测试一下 echo 命令是否可以获取进程退出码:
#include <iostream>
int main()
{
return 10;
}
使用该指令发现父进程 shell 成功获得了退出码:
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ./term.o
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ echo $?
10 # 获取退出码成功
代码运行完毕结果不正确
接下来我们学习两个函数:
_exit 函数
_exit 函数是一个系统函数,调用该函数后会直接将进程给终止掉,不做任何事情,因此我们调用该函数后缓冲区中的数据是无法被清理的:
#include <iostream>
#include <unistd.h>
int main()
{
// 未换行的数据会被存储在缓冲区当中
std::cout << "hello, Linux!";
_exit(0);
}
运行后会发现什么结果都看不到:
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ./term.o
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤
exit 函数
exit 函数是 C 标准库中的函数,该函数不是系统函数,他会在被调用时先执行用户定义的清理函数以及缓冲区数据,最后再调用 _exit() 函数
我们将 _exit 函数换成 exit 函数:
#include <iostream>
#include <unistd.h>
int main()
{
// 未换行的数据会被存储在缓冲区当中
std::cout << "hello, Linux!";
exit(0);
}
我们会发现缓冲区中的数据被刷新出来了:
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ./term.o
hello, Linux!%
下面这张图很好的比较出来两个函数的区别:

除此之外,有些函数在执行失败后会给出一个错误码,但我们并不知道这个错误码对应的是什么含义,此时,我们可以通过一个函数来将退出码转化为我们可以看懂的字符串:
strerr 函数
通过这个函数,我们可以把错误码转化为我们可以看懂的字符串:
#include <iostream>
#include <unistd.h>
#include <cstring>
int main()
{
for(int i = 1; i <= 5; ++i)
std::cout << strerror(i) << std::endl;
exit(0);
}
执行结果如下:
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ./term.o
Operation not permitted
No such file or directory
No such process
Interrupted system call
Input/output error
在 C 语言中,errno 是一个全局变量(通常是线程局部的),用于存储最近一次系统调用或库函数调用失败的错误代码,结合 strerror 函数,我们就可以将 errno 对应的错误信息
打印出来了:
#include <iostream>
#include <unistd.h>
#include <cstring>
int main()
{
// 分配一块超大内存使得 malloc 函数调用出错
size_t huge_size = 1UL << 40;
int *p = (int *)malloc(huge_size);
std::cout << errno << ":" << strerror(errno) << std::endl;
return 0;
}
可以看到,有了 errno 和 strerror 两个函数的加持,我们就能够很轻松的获取到函数==系统调用或库函数调用失败的错误信息了
╭─ljx@VM-16-15-debian ~/linux_review/lesson1/dir1
╰─➤ ./term.o
12:Cannot allocate memory
代码异常终止
代码异常终止通常是因为进程执行了某些非法操作,触发了操作系统发送的 信号(Signal),导致进程被强制终止。
1. 信号(Signal)是异常终止的直接原因
当进程执行了不被允许的操作(如访问非法内存、除零、执行非法指令等),操作系统会向该进程发送一个信号,默认行为可能是终止进程(甚至产生核心转储 core dump)。常见信号包括:
| 信号名 | 信号值 | 触发原因 | 默认行为 |
|---|---|---|---|
| SIGSEGV | 11 | 非法内存访问(如解引用空指针、越界访问数组) | 终止 + core dump |
| SIGFPE | 8 | 算术错误(如除零、整数溢出) | 终止 + core dump |
| SIGILL | 4 | 执行非法指令(如损坏的二进制文件、CPU 不支持的指令) | 终止 + core dump |
| SIGBUS | 7 | 总线错误(如对齐访问失败,硬件错误) | 终止 + core dump |
| SIGABRT | 6 | 程序主动调用 abort()(通常因断言失败或检测到不可恢复错误) |
终止 + core dump |
| SIGKILL | 9 | 强制终止(如 kill -9,不可捕获或忽略) |
立即终止 |
| SIGTERM | 15 | 优雅终止请求(如 kill 默认发送的信号,允许程序清理资源) |
终止 |
2. 代码异常终止的常见场景
(1) 段错误(Segmentation Fault, SIGSEGV)
原因:访问了不属于进程的内存(如空指针解引用、数组越界)。
示例:
int *p = NULL;
*p = 42; // 解引用空指针,触发 SIGSEGV
(2) 浮点/算术异常(SIGFPE)
原因:除零、整数溢出等。
示例:
int a = 10 / 0; // 除零,触发 SIGFPE
(3) 主动调用 abort()(SIGABRT)
原因:程序检测到致命错误(如断言失败)。
示例:
assert(1 == 2); // 断言失败,触发 SIGABRT
(4) 非法指令(SIGILL)
原因:执行了 CPU 不支持的指令(如损坏的二进制文件)。
示例(人为构造):
void (*func)() = (void (*)())0xDEADBEEF;
func(); // 跳转到非法地址,可能触发 SIGILL
(5) 外部终止(SIGKILL/SIGTERM)
原因:用户或系统管理员通过 kill 命令终止进程。
示例:
kill -9 <PID> # 发送 SIGKILL,强制终止进程
信号部分将会在后面讲解,这里做一个简单的理解即可