Linux 信号

1.前台进程和后台进程

比如我这个前台指令 当我休眠的时候我依然可以接收终端信号 但是不会立即执行

比如 ll ls -l pwd当sleep结束玩后立马执行

但是当我把sleep10& 后台执行的时候 我的终端可以接着输入其他指令而不会休眠10秒

前台进程只能有一个 但是后台进程可以有多个

前台进程:会接收终端发出的信号(比如你在终端按快捷键触发的信号)
例:按 Ctrl+C 会给前台进程发 SIGINT 信号,进程会直接终止。
后台进程:默认不接收终端发出的信号(终端快捷键对它无效),只能通过 kill 命令手动发送信号。

2.主要信号

主要的信号有这些

  1. 标准信号(传统信号)

编号范围:1~31 号(对应你列表里的 1) SIGHUP 到 31) SIGSYS)

特点:是 Linux 早期定义的传统信号,存在 "信号丢失" 问题(同一信号多次发送可能只处理一次),功能偏向基础进程控制(终止、暂停、异常通知等)。

为什么刚好是1到31号呢 int刚好是32位 相当于每一位都可以表示一个信号

int刚好是32位

  1. 实时信号

编号范围:34~64 号(对应你列表里的 34) SIGRTMIN 到 64) SIGRTMAX)

特点:是后期扩展的信号,支持 "可靠传递"(同一信号多次发送会按序处理,不会丢失),还能携带额外数据,主要用于进程间的实时通信场景。

没有32和33号信号!!!

我们目前主要学习标准信号

3.signal

作用是指定进程收到某个信号时要执行的动作。

cpp 复制代码
#include <signal.h>
// 定义:为 signum 信号设置处理函数 handler
void (*signal(int signum, void (*handler)(int)))(int);

通过这个函数我们可以验证一件事:我们平时使用的ctrl+c强制终止(对前台进程)

本质上就是2号信号

写一个代码测试一下就好了

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

void handle_sigint(int sig) {
    printf("\n收到SIGINT信号(编号:%d),不终止进程!\n", sig);
}

int main() {
    signal(SIGINT, handle_sigint);

    while (1) {
        printf("进程运行中...按Ctrl+C试试\n");
        sleep(2);
    }
    return 0;
}

我们发现我们通过signal函数重新改变二号信号的执行动作

但是我们发现我们的ctrl+c就会执行二号信号的动作

因此我们可以断定ctrl+c本质上就是向进程发送二号信号

但是有些信号signal是没有用的

不然就会出现不可控的现象

比如你无法让一个死循环的进程终止

对于普通信号 sigal对下面这两个信号是没有用的

不信我们可以代码测试一下

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

void handle_sigint(int sig) {
    printf("\n收到SIGINT信号(编号:%d),不终止进程!\n", sig);
}

int main() {
    signal(SIGKILL, handle_sigint);
    while (1) {
        printf("进程运行中...我的pid是%d\n",getpid());
        sleep(20);
    }
    return 0;
}

4.kill

我们前面使用的kill函数 本质上是一个函数封装的

cpp 复制代码
#include <signal.h>   // 信号相关定义
#include <sys/types.h> //  pid_t 类型定义(进程ID类型)
int kill(pid_t pid, int sig);

成功:返回 0;

失败:返回 -1,并设置全局变量 errno 指示错误原因(如进程不存在、权限不足等)。

pid 的取值决定了信号发送的目标

sig 是信号的编号或宏定义

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

int main() {
    while (1) {
        printf("进程运行中...我的pid是%d\n,进程20s后会接收到9号信号",getpid());
        sleep(20);
        kill(getpid(),SIGKILL);
        printf("没有接收到9号信号 进程未被终止\n");
    }
    return 0;
}

运行结果符合我们的预期 程序接收到终止信号 所以 printf("没有接收到9号信号 进程未被终止\n");及其后面的语句没有被执行

4.异常

当我们异常的时候也会产生信号

这里我们以除0为例

使用空指针也是同理 只不过信号不一样而已

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

void fpe_handler(int sig) {
    printf("捕获到信号:%d(SIGFPE),原因:除0异常!\n", sig);
    sleep(20);
}

int main() {
    signal(SIGFPE, fpe_handler);

    printf("开始执行,准备触发除0...\n");
    int a = 10;
    int b = 0;
    int c = a / b;

    printf("计算结果:%d\n", c);
    return 0;
}

这个程序运行结果有一点和我们理解的一样

就是当除0时候操作系统会给进程发信号 但是

1.为什么这个信号会被一直执行呢

2.并且操作系统怎么知道我除0了???

CPU 中存在标志 / 控制寄存器(属于当前进程的硬件上下文,仅与该进程绑定,不会影响其他进程)。当执行 "除以 0" 的运算时,CPU 会触发除法错误异常,此时寄存器会记录该异常的状态(并非 "溢出标志位",溢出标志位对应算术溢出场景),而非简单的 "0 变 1"。

此时 CPU 会暂停当前进程、切换到内核态,由操作系统的 "异常处理程序" 接管;操作系统识别出该异常的类型是 "除 0" 后,会向对应的进程发送SIGFPE信号,完成 "异常→信号" 的转换。

处理函数只 "响应" 了信号,没 "解决" 触发信号的除 0 问题,所以除 0 代码会被反复执行,信号也反复触发。

5.core dump标志

我们前面学习过status结构 但是没有介绍core dump标志

正常终止:高 8 位存exit()的返回值,低 8 位全 0;

被信号所杀:

低 7 位(图中 "终止信号"):存终止进程的信号编号(比如 SIGSEGV 是 11、SIGFPE 是 8);

第 8 位(图中 "core dump 标志",即低 8 位的第 7 位):存0/1,表示进程被该信号终止时,是否生成了 core 文件。

coredump在接收到一些信号的时候coredump会变成1

同时生成core文件方便我们事后调试

我们可以看到哪些信号会生成core dump

Linux 进程退出和进程控制-CSDN博客

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

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        int a = 10 / 0;
        exit(0);
    } else if (pid > 0) {
        int status;
        waitpid(pid, &status, 0);
        // 直接提取位7(第8位)的值:0=未生成core,1=生成core
        int core_flag = (status >> 7) & 1;
        printf("core标志位(第8位)值:%d\n", core_flag);
    }
    return 0;
}

core 文件是程序崩溃瞬间的 "内存镜像 + 运行上下文快照",核心是程序崩溃时的运行状态数据,主要包含这些内容:

1.进程地址空间的内容

包括程序的代码段(执行的指令)、数据段(全局 / 静态变量)、堆(动态分配的内存,比如malloc的内容)、栈(函数调用链、局部变量、参数等)。

2.CPU 寄存器的状态

崩溃瞬间 CPU 各个寄存器(如程序计数器 PC、栈指针 SP、基址指针 BP 等)的值,能直接反映程序崩溃时正在执行的指令位置。

3.进程的上下文信息

比如进程 ID、崩溃时触发的信号(如 SIGFPE、SIGSEGV)、进程的资源占用状态等。

4.动态链接库的相关信息

程序依赖的动态库加载地址、链接关系等,方便调试时匹配对应的库文件。

这些内容的作用是还原崩溃现场------ 用调试工具(如gdb)加载 core 文件,就能查看崩溃时的函数调用链、变量值、执行位置,定位崩溃原因。

6.alarm

cpp 复制代码
#include <unistd.h>
unsigned int alarm(unsigned int seconds);

seconds 定时秒数(0 表示取消当前定时器)

返回值 之前定时器的剩余秒数(无则返回 0)

进程同一时刻最多只有 1 个生效的闹钟,不存在 "多个未响闹钟排队" 的情况

调用 alarm(sec) 后,内核启动一个秒级定时器;

定时器到期时,内核向进程发送 SIGALRM 信号;
若不自定义 SIGALRM 信号处理函数,进程会被默认终止(需用 signal/sigaction 自定义处理逻辑)。

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

void sigalrm_handler(int sig) {
    printf("Alarm triggered! SIGALRM received 是14号编号\n");
}

int main() {
    signal(SIGALRM, sigalrm_handler);
    alarm(3);
    while(1);
    return 0;
}

7.abort

cpp 复制代码
void abort(void);

abort 是C 标准库中的函数,用于异常终止当前进程,属于紧急情况下的程序终止手段。它的核心设计目标是在程序检测到不可恢复的错误时,立即终止并留下调试线索(如核心转储文件)。

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

int main() {
    // 未换行,缓冲区未刷新,abort后内容可能无法输出
    printf("检测到严重错误,即将终止程序");
    abort(); // 程序异常终止,后续代码不会执行
    printf("这行永远不会执行");
    return 0;
}
相关推荐
Lynnxiaowen2 小时前
今天我们学习kubernetes内容持久化存储
linux·运维·学习·容器·kubernetes
KingRumn2 小时前
Linux进程间通信之消息队列
linux·服务器·网络
jerryinwuhan2 小时前
1210_linux_2
linux·运维·服务器
IDC02_FEIYA2 小时前
Linux怎么看服务器状态?Linux查看服务器状态命令
linux·运维·服务器
爱潜水的小L2 小时前
自学嵌入式day28,文件操作
linux·数据结构·算法
刚入坑的新人编程2 小时前
Linux(小项目)进度条演示
linux·运维·服务器
IT运维爱好者2 小时前
【Linux】Python3 环境的下载与安装
linux·python·centos7
Apibro2 小时前
【LINUX】时区修改
linux·运维·服务器
遇见火星2 小时前
Linux性能调优:使用strace来分析文件系统的性能问题
linux·运维·服务器·strace