Linux 进程终止

Linux 进程终止

七月 15, 2025 次阅读

一个进程退出一般分为以下这三种情况:

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

一个进程退出后会有一个退出码,该退出码是给父进程看的,子进程由父进程创建的,父进程自然是需要关心子进程的情况的,也正是因此,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!%         

下面这张图很好的比较出来两个函数的区别:

addr

除此之外,有些函数在执行失败后会给出一个错误码,但我们并不知道这个错误码对应的是什么含义,此时,我们可以通过一个函数来将退出码转化为我们可以看懂的字符串:

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,强制终止进程

信号部分将会在后面讲解,这里做一个简单的理解即可