云计算百科
云计算领域专业知识百科平台

Linux系统篇29——信号(一):从 Ctrl+C 到内核位图,信号“是什么、从哪来“一次讲透

📚 本文收录于「流浪」的系列专栏

系列专栏直达链接
🐧 Linux系统 进入专栏 →
⚙️ C++ 进入专栏 →
📊 数据结构与算法 进入专栏 →
🐍 Python 进入专栏 →
🔗 LangChain & LangGraph 进入专栏 →
🗄️ MySQL 数据库 进入专栏 →
🌿 Git 工具 进入专栏 →
🌐 计算机网络 进入专栏 →
🤖 AI 进入专栏 →
💯 大厂面试、八股 进入专栏 →
📚 学习筑基专栏 进入专栏 →

🏠 博客主页:流浪 | 📝 原创首发于 CSDN

前言:篇28 把信号量讲透了——计数器、P/V、电影院订票。但通信系列里还有个"信号(signal)"一直没单独立篇。它和信号量只差一个字,机制却完全无关:信号是 OS 给进程的一记"异步通知",Ctrl+C、除零崩溃、管道写穿全靠它。这篇把信号从"是什么"讲到"内核怎么发",顺手把和信号量的区别钉死。


一个问题:你按下 Ctrl+C,背后到底发生了什么?

你按下 Ctrl+C,前台进程瞬间没了——可这背后 OS 到底干了什么?为什么是"瞬间",为什么只能杀前台、杀不掉后台,为什么子进程变成孤儿就按不死?

再想想:程序里写个 while(1) 除个零,进程直接被内核"崩掉"还吐个 core,这"崩"又是谁干的?

这些日常现象背后,站着同一个机制——信号。它不是你主动调的函数,是 OS 在关键时刻"拍你一下"的通知。本篇先把信号"是什么、从哪来、发给谁"讲透。


一、信号到底是什么

1.1 一句话:进程间的一记"异步通知"

信号的准确定义是:进程间的事件通知机制,更准确地说是 OS 给进程发的通知。

它和管道、消息队列最本质的区别在于:信号不带数据,只通知"某类事件发生了",具体怎么响应由进程自己定。管道是传字节流、消息队列是传数据块,信号是"有件事发生了,你自己看着办"。这一点决定了它轻量,但也决定了它不适合传业务数据。

1.2 生活里咋理解:闹钟、红绿灯、狼烟

  • 闹钟:你睡觉时不知道几点响,响了你才反应——你没有被实时盯着,事件到了才响应,这就是异步。
  • 红绿灯:你过马路不被交警实时指挥,灯变了你自行决定走还是停。
  • 狼烟:边防不派信使跑全程,点一炷烟通知"有敌情",远处自己解读。

它们的共同点:发通知的人不盯着你处理,你该干啥干啥,事件到了你再响应。

1.3 关键:它跟进程运行是"异步"的

"异步"是信号最难理解、也最重要的一个词。

异步 = 信号在进程运行的任意时刻都可能送达,不挑进程停在哪行代码上。进程不能像 read 那样"主动阻塞等信号",信号是 OS 在中断返回、系统调用返回等时机"插进来"的。

这带来一个必然结果:信号不能到了就立刻处理,必须先记下、等合适时机再处理。(二)里讲发送机制时会看到,这个"先记下"就是内核用位图记录信号的由来。

1.4 先记住四条

  • 早知道怎么处理:信号处理方式是信号还没产生前就注册好的,OS 天生给每个信号准备了默认处理动作。
  • 不立即处理:收到信号不会马上打断当前指令去执行处理,而是等到从内核态返回用户态等合适时机才处理。
  • OS 内置识别:进程之所以能识别信号,是因为内核在创建进程时就内置了整套信号机制。
  • 信号源非常多:键盘、系统调用、硬件异常、软件条件、命令,都能产生信号(下一节展开)。
  • 1.5 查看Linux操作系统信号的命令

    kill -l


    二、信号不等于信号量,别搞混【回指篇28】

    2.1 为啥老有人混:就差一个字

    "信号 signal" 和 "信号量 semaphore",中文只差一个"量"字,但机制是两个世界。很多初学者学到这就晕,所以专门拎出来说清楚。

    2.2 一张表看明白

    维度信号 signal信号量 semaphore
    是什么 异步事件通知 同步互斥的计数器
    干什么 告诉进程"某事件发生了"(如 SIGKILL、SIGSEGV) 管"资源还剩几份、谁能进临界区"
    谁发的 OS(键盘 / 异常 / 进程请求触发) 进程自己通过 semop 操作
    带数据吗 不带,只通知 带计数值
    怎么用 kill 发送 / signal 注册处理函数 semget / semop 做 P、V

    2.3 一句口诀记住

    篇28 第九节 9.1 已经专讲过这个对比,这里只回指不动重讲:"信号是叫你一下,信号量是数你一下。" 一个字之差,两个世界,面试被问到这句话答出来就够。


    三、信号都是从哪冒出来的

    3.1 五种来源

    • 键盘:Ctrl+C → 给前台发 SIGINT。
    • 系统调用:kill() / raise() / abort() 主动发。
    • 硬件异常:除零 → SIGFPE;解引用空/野指针 → SIGSEGV。
    • 软件条件:管道读端关了还写 → SIGPIPE;alarm 计时到点 → SIGALRM。
    • 系统命令:kill 命令给进程发信号。

    3.2 为啥来源这么多:OS 是唯一总入口

    看起来来源五花八门,但有一个统一点:无论哪条路,最终都由内核产生并投递信号。

    用户进程自己不能直接"改写"另一个进程的信号状态,必须经过内核。所以信号看似来源杂,实则统一收口在 OS——这个点先记住,下一篇讲发送机制时就是它的展开。


    四、普通信号(1–31)是什么

    普通信号(传统信号):属于早期 Unix 信号,编号 1‑31,不排队,同一个信号多次到来只会保留一次,信号会合并丢失,进程处理时可以被其他信号打断,不保证接收顺序,适用于终止、中断这类简单通知场景。

    用 kill -l 能看到系统一共 64 个信号,编号 1–64。1–31 是标准(普通)信号,32–64 归实时信号(下一节讲)。先说普通信号怎么来的。

    4.1 键盘按出来的:Ctrl+C

    Ctrl+C 给当前前台进程组发 SIGINT(2),默认动作是终止进程。

    注意"前台进程组"这四个字,它解释了为什么后台进程按 Ctrl+C 杀不掉——这点第六章专门讲。

    4.2 代码里发:kill() 和 raise()

    kill(pid, sig):给指定进程发信号(有限制:要么同用户,要么 root)。

    int kill(pid_t pid, int sig);

    raise(sig):给自己发,等价于 kill(getpid(), sig)。

    int raise(int sig);

    4.3 自己干自己:abort() 发 6 号

    abort() 是 C 标准库函数,它给自己发 SIGABRT(6),而且它保证进程最终一定终止——即使你自定义捕获了 SIGABRT,只要 handler 返回,abort() 会先把处置重置成默认,再发一次,直到进程被干掉。

    void abort(void);

    为什么 abort 一定能终止、而 kill/raise 发的普通信号不一定?这是个经典追问,留到(二)第六篇 细讲。

    4.4 硬件出错:除零、野指针

    • 除零 → SIGFPE(8)(floating-point exception,虽叫浮点异常,整数除零也归它)。
    • 解引用空指针/野指针 → SIGSEGV(11)(segmentation violation)。

    关键点:这些不是你代码主动调函数触发的,是 CPU 执行指令出错,内核捕获后转成信号发给进程。所以你看到的"程序崩了",本质是内核给你发了个信号、默认动作是终止+core。

    4.5 软件条件:管道写穿 SIGPIPE、alarm 计时

    • 管道读端关闭后还往里写 → 内核发 SIGPIPE(13),默认终止写进程。写网络程序时这个信号常被忽略或自定义处理,否则一个对端断开就崩。
    • alarm(seconds) 计时到点 → 内核发 SIGALRM(14),常用于超时控制。

    4.6 命令行:kill 命令

    kill -9 PID 发 SIGKILL(9);kill -l 列出全部信号名。记住 SIGKILL=9,它是面试和排障都高频的信号(第七章 7.4 会讲它为什么谁都拦不住)。


    五、实时信号(34–64)是什么

    5.1 实时信号是什么

    普通信号(传统信号):属于早期 Unix 信号,编号 1‑31,不排队,同一个信号多次到来只会保留一次,信号会合并丢失,进程处理时可以被其他信号打断,不保证接收顺序,适用于终止、中断这类简单通知场景。

    内核视角,实时信号编号是 32–64(共 33 个)。但 glibc 的 NPTL 线程库内部占用了 32、33 两个,所以用户程序真正可用的实时信号从 SIGRTMIN(通常是 34)起。

    真实内核里 32/33 也是实时信号编号,只是被线程库内部用掉了,普通程序碰不到。面试被问到"实时信号从几号开始",答"内核32起,但用户态通常34起"最严谨。

    5.2 跟普通信号差在哪

    维度普通信号(1–31)实时信号(34–64)
    排队 不排队,重复发可能丢失 支持排队,不丢失
    顺序 多个未决时按编号从小到大 按优先级 / 发送顺序
    带数据 不带 可用 sigqueue 带附加数据

    5.3 点到为止

    面试常问"实时信号解决什么问题"——就是普通信号会丢失、不排队的短板。日常业务用普通信号足够,实时信号用在不能丢事件的场景(如高速数据采集)。本篇不深,知道差别即可。


    六、信号发给谁:前台还是后台【回指前面文章】

    6.1 什么是前台进程、后台进程

    前台进程:占据终端交互界面的进程,直接接收键盘输入,终端被它占用,执行时用户不能在该 shell 敲其他命令,Ctrl+C 可以终止它。 后台进程:脱离终端输入,在系统后台默默运行,不抢占命令行,shell 可以继续接收新命令;但它的输出依旧会打印到终端,收到 SIGINT 信号不会终止,一般用&符号把程序放到后台运行。

    6.2 关键:键盘信号只发给前台

    Ctrl+C 这类键盘信号,只投递给前台进程组。后台进程不在前台进程组里,所以收不到键盘信号。

    这就是你在终端跑 ./a.out & 之后,按 Ctrl+C 杀不掉它的原因——它已经不是前台了。

    6.3 前台只能一个、后台能多个

    一个终端同一时刻只有一个前台进程组(键盘输入总得有个明确目标),但后台可以有多个进程。这个"唯一前台"的约束,是键盘信号能精确投递的前提。

    6.4 再识孤儿进程

    • Ctrl+C  发送 SIGINT信号,只作用于当前前台进程组。
    • 父进程退出 → 子进程成为孤儿,被  init/systemd  收养(由前台进程转变为后台进程),脱离当前终端进程组。
    • 此时按  Ctrl+C ,信号不会发给孤儿进程,所以杀不死。

    6.5 如何进行前后台转换

    jobs //查看后台任务

    fg +任务号 //将特定的进程提到前台

    Ctrl+Z //暂停进程 也可以将前台进程挂到后台

    bg +任务号 //将后台进程运行起来


    七、面试官爱问(带答案)

    7.1 信号为什么是"异步"的?

    答:信号可在进程运行的任意时刻由 OS 投递,进程并不主动等待它;OS 只在从内核态返回用户态等时机才检查并处理挂起信号,所以天然异步。正是异步性,决定了信号必须"先记录、后处理"。

    7.2 普通信号和实时信号到底差在哪?

    答:普通信号(1–31)不排队、重复发送可能丢失;实时信号(用户态通常 34 起)支持排队、不丢失,还能通过 sigqueue 带附加数据。普通信号够日常用,实时信号用于不能丢事件的场景。

    7.3 Ctrl+C 为什么只能杀前台进程?

    答:按下 Ctrl+C,终端驱动会向前台进程组发送 SIGINT(中断信号)。后台进程不属于当前终端的前台进程组,因此接收不到这个键盘产生的 SIGINT 信号,所以 Ctrl+C 无法终止后台进程。想要杀掉后台进程,需要用 kill 命令主动给它发送信号。

    7.4 SIGKILL(9) 为什么谁都杀得死、还拦不住?

    答:POSIX 规定 SIGKILL 和 SIGSTOP 是两个不可捕获、不可阻塞、不可忽略的信号(man7 signal.7 原文:"cannot be caught, blocked, or ignored")。连默认处理函数都换不掉,所以必杀——这也是为什么 kill -9 是最后的手段。

    7.5 abort() 发 6 号为什么能终止、kill/raise 却不一定?

    答(钩子):abort() 发 SIGABRT(6),但它对 SIGABRT 有特殊保证——你若捕获了且 handler 返回,它会把处置重置成默认再发一次,忽略也一样重置再发,所以最终一定终止。而 kill/raise 若发的是可被捕获/忽略的信号(如 SIGTERM),进程完全可以自定义 handler 选择不终止。完整机制留(二)第六篇 6.1 展开。


    八、小结

    用三句话收尾:

  • 是什么:信号是 OS 给进程的异步事件通知,不带数据,只告诉你"某事件发生了"。
  • 从哪来:键盘、系统调用、硬件异常、软件条件、命令五路来源,最终统一收口内核;普通信号 1–31,实时信号用户态通常 34 起。
  • 发给谁:键盘信号只发前台进程组,后台收不到;信号和信号量只差一字、机制无关。
  • 下一篇(二)钻进内核:信号产生后怎么被记下来、又是谁、怎么"写"进目标进程的,进程收到后怎么应对,以及把信号和信号量、管道的区别彻底钉死。


    💬 看完如果有一句没懂,或者你手上的课件图跟我这里解读对不上,评论区直接说,我补图补例子。也欢迎把本篇和篇28(信号量)串起来复习——它俩名字像,但真不是一个东西。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Linux系统篇29——信号(一):从 Ctrl+C 到内核位图,信号“是什么、从哪来“一次讲透
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!