OS56.【Linux】理解信号: 信号的产生(1) 键盘输入和kill命令

OS56.【Linux】理解信号: 信号的产生(1) 键盘输入和kill命令

目录

1.信号的定义

生活中的例子

产生信号后,可能不会立即处理

结论

2.从Ctrl+C来看信号的产生

为什么 Ctrl+C 可以杀死进程?

Bash内部对Ctrl+C做了特殊处理

3.信号的种类

细节1: 信号的编号

普通信号

实时信号

细节2: 信号的名称

4.形象理解信号的处理方式

5.测试信号的捕捉

signal系统调用

反思: 能否捕获所有信号?

反思: 为什么进程无法捕获9和19号信号?

Linux内核源码验证: 为什么进程无法捕获9和19号信号


之后的文章将按这个顺序来: 

        信号的产生→信号的保存→信号的捕捉

1首先说明: 信号和信号量没有任何关系! 

        注: 信号量在OS56.【Linux】理解信号量文章讲过

生活中的例子

从生活中的例子理解信号: 信号弹、上下课铃声、闹钟等等都可以认为是信号

那为什么能认为"信号弹、上下课铃声、闹钟"是信号呢?

答: 有人提前教过我们,我们我记住了这些就是信号,可以推出: 即使是现在没有信号产生,我们也知道信号产生之后应该干什么,例如红灯停,绿灯行 → 识别信号,知道信号的处理方法

产生信号后,可能不会立即处理

信号产生了,由于某些原因(例如当前处理的事情非常重要,无法暂停),我们可能并会不立即处理这个信号,在合适的时候才会处理

所以,信号产生后到信号处理这个时间窗口内,进程必须记住信号的到来

结论

1.进程必须识别且能够处理信号: 即使信号没有产生,也要具备处理信号的能力,信号的处理能力属于进程内置功能的一部分

2.当进程真的收到了一个具体的信号的时候,进程可能并不会立即处理这个信号,合适的时候才会去执行对应的动作,那么进程在这个时间窗口需要具有临时保存哪些信号已经发生了的能力

2.从Ctrl+C来看信号的产生

新建以下文件:

test_signal/ ├── makefile ├── send_signal.sh └── test_signal.cpp

makefile写入:

test_signal.out:test_signal.cpp g++ -o $@ $^ -g -std=c++11 .PHONY:clean clean: rm -f test_signal.out

test_signal.cpp写入无限向终端打印hello world的程序:

#include <unistd.h> #include <iostream> using namespace std; int main() { for (;;) { std::cout<<"Hello World!"<<std::endl; sleep(1); } return 0; }

启动:

/test_signal.out 

运行结果: 按下Ctrl+C,进程停止执行

为什么 Ctrl+C 可以杀死进程?

上方运行的结果可以看到: 输入任何bash命令都不会起作用,但是Ctrl+C杀死了这个进程

Linux中,一次登陆分配一个终端,一般会配上一个bash,每一个登陆,只允许一个进程是前台进程,可以允许多个进程是后台进程

那么前台进程和后台进程的区别是: 只有前台进程能获取键盘输入

那么无限向终端打印hello world的进程为前台进程,bash为后台进程,这样bash无法获取键盘输入

如果将无限向终端打印hello world的进程为启动为后台进程:

/test_signal.out &

运行结果: Ctrl+C无法结束无限向终端打印hello world的进程为启动的后台进程

Bash内部对Ctrl+C做了特殊处理

那Ctrl+C为什么没有杀死bash? 因为Bash内部对Ctrl+C做了特殊处理,Ctrl+C是向前台进程发送了2号信号来终止前台进程的(这个后面的文章再解释)

在bash-5.3的根目录下有一个sig.c文件,注释说明了原因:

#define NULL_HANDLER (SigHandler *)SIG_DFL /* The list of signals that would terminate the shell if not caught. We catch them, but just so that we can write the history file, and so forth. */ static struct termsig terminating_signals[] = { //...... #ifdef SIGINT { SIGINT, NULL_HANDLER, 0 }, #endif //...... };

注释里面说的很清楚: 这些信号如果不捕获(捕获这个概念在本文 4.形象理解信号的处理方式 提到了)的话,会终止shell

附: bash-5.3的国内下载链接https://mirrors.nju.edu.cn/gnu/bash/bash-5.3.tar.gz

结论: Ctrl+C 可以杀死前台进程,Ctrl+C向前台进程发送2号信号SIGINT

3.信号的种类

kill -l命令查看系统的各个信号:

细节1: 信号的编号

仔细看的话,从1到64,没有32号和33号信号(历史原因,这里不讲),一共62个信号

普通信号

1号~31号

实时信号

34号~64号

实时信号的特点: 实时信号产生了必须立即处理,这里不学

细节2: 信号的名称

信号的名称的字母都是大写的,其实它们在Linux内核中被定义为宏,信号的名称的宏对应的数字就是信号的编号

x86平台下,Linux内核定义在/arch/x86/include/uapi/asm/signal.h中

*注: uapi这个目录下存储用户空间api头文件

#define SIGHUP 1 #define SIGINT 2 #define SIGQUIT 3 #define SIGILL 4 #define SIGTRAP 5 #define SIGABRT 6 #define SIGIOT 6 #define SIGBUS 7 #define SIGFPE 8 #define SIGKILL 9 #define SIGUSR1 10 #define SIGSEGV 11 #define SIGUSR2 12 #define SIGPIPE 13 #define SIGALRM 14 #define SIGTERM 15 #define SIGSTKFLT 16 #define SIGCHLD 17 #define SIGCONT 18 #define SIGSTOP 19 #define SIGTSTP 20 #define SIGTTIN 21 #define SIGTTOU 22 #define SIGURG 23 #define SIGXCPU 24 #define SIGXFSZ 25 #define SIGVTALRM 26 #define SIGPROF 27 #define SIGWINCH 28 #define SIGIO 29 #define SIGPOLL SIGIO /* #define SIGLOST 29 */ #define SIGPWR 30 #define SIGSYS 31 #define SIGUNUSED 31 /* These should not be considered constants from userland. */ #define SIGRTMIN 32 #define SIGRTMAX _NSIG

4.形象理解信号的处理方式

操作系统为进程发送信号,进程需要处理信号,只有3种方式,而且只能3选1

默认动作: 手机响了,接听电话(这是大多数人默认的动作)

        从上面的实验的运行结果,可以得出: 进程收到2号信号的默认动作,就是终止自己

忽略:
手机响了,选择静音,不接听,忽略这个电话

自定义动作: 手机响了,但不想接,于是挂断电话并回复短信(自定义动作)

自定义动作被称为信号的捕获(或捕捉),对于bash而言,不能执行2号信号的默认动作,需要捕获该信号做进一步处理,否则影响用户体验

下面测试信号的捕捉

5.测试信号的捕捉

验证Ctrl+C向进程发送了2号信号,那么就要使用自定义方法捕获2号信号,这里使用signal系统调用

signal系统调用

signal是修改特定进程对于信号的处理动作的

signum: 信号的编号

handler: 函数指针,手册给出:

typedef void (*sighandler_t)(int);

sighandler_t为函数指针类型,这个函数执行捕获信号后的自定义动作,其返回值为void,只接受一个int类型的信号编号

为什么myhandler需要传参?

答: 区分不同的信号,做不同的事情,例如:

void myhandler(int signo) { if (signo==1) { //...... } else if (signo==2) { //...... } else if (...) //...... } int main() { signal(1,my_hander); signal(2,my_hander); //...... }

test_signal.cpp:无限循环正常执行,只不过用户键入Ctrl+C后需要执行自定义动作

#include <unistd.h> #include <iostream> #include <signal.h> void myhandler(int signo) { std::cout<<"已执行自定义动作,信号编号为"<<signo<<"号"<<std::endl; } int main() { signal(SIGINT,myhandler); for (;;) { std::cout<<"Hello World!"<<std::endl; sleep(1); } return 0; }

注: 1.signal(SIGINT,my_hander);只需要设置一次,后面都有效

      2.只有收到2号信号时(例如Ctrl+C或kill -2 pid),myhandler才会调用,调用signal不会触发myhandler的执行

运行结果:test_signal.out进程遇到2号信号不会执行默认动作,而是执行打印任务

反思: 能否捕获所有信号?

如果将系统中所有信号全部捕捉,执行自定义动作,例如自定义动作为无限循环,那么是不是test_signal.out进程无法被杀死?

这里测试1~31号普通信号:

#include <unistd.h> #include <iostream> #include <signal.h> #include <sys/types.h> void myhandler(int signo) { std::cout<<"已执行自定义动作,信号编号为"<<signo<<"号"<<std::endl; } int main() { for (int i=1;i<=31;i++) signal(i,myhandler); std::cout<<"本进程的pid为: "<<getpid()<<std::endl; for (;;) { std::cout<<"Hello World!"<<std::endl; sleep(1); } return 0; }

启动一个脚本,批量向该进程发送信号:

send_signal.sh写入:

#!/usr/bin/bash if [ "$1" = "" ] || [ "$1" = " " ] then echo "需要提供进程的pid,用法: ./send_signal.sh pid" exit -1 fi pid=$1 for ((i=1; i<=31; i++)) do kill -${i} ${pid} sleep 0.2 done

运行结果: 9号信号SIGKILL无法捕获

从10号信号开始:

#!/usr/bin/bash if [ "$1" = "" ] || [ "$1" = " " ] then echo "需要提供进程的pid,用法: ./send_signal.sh pid" exit -1 fi pid=$1 for ((i=10; i<=31; i++)) do kill -${i} ${pid} sleep 0.2 done

运行结果: 19号信号SIGSTOP无法捕获        

从20号信号开始:

#!/usr/bin/bash if [ "$1" = "" ] || [ "$1" = " " ] then echo "需要提供进程的pid,用法: ./send_signal.sh pid" exit -1 fi pid=$1 for ((i=20; i<=31; i++)) do kill -${i} ${pid} sleep 0.2 done

运行结果: 20~31号信号可全部被捕获

结论: 1~31号普通信号,只有9号信号SIGKILL、19号信号SIGSTOP无法捕获   

反思: 为什么进程无法捕获9和19号信号?

如果进程捕获了所有信号但是不退出,这样的进程十分危险,它一直占用操作系统的资源,而且可能执行一些危险的动作

为了避免这样的事情发生,Linux中设置: 9和19号信号无法捕获(可以理解为这两个信号是操作系统留的"后门"),分别用于强制终止进程和强制暂停进程,否则系统将彻底失去对进程的最终控制权

结论: 内核必须在任何情况下都能无条件终止或停止进程,以保证系统的可控性和稳定性

Linux内核源码验证: 为什么进程无法捕获9和19号信号

linux 6.18.6在/kernel/signal.c中定义:

static bool sig_task_ignored(struct task_struct *t, int sig, bool force) { void __user *handler; handler = sig_handler(t, sig); /* SIGKILL and SIGSTOP may not be sent to the global init */ if (unlikely(is_global_init(t) && sig_kernel_only(sig))) return true; if (unlikely(t->signal->flags & SIGNAL_UNKILLABLE) && handler == SIG_DFL && !(force && sig_kernel_only(sig))) return true; /* Only allow kernel generated signals to this kthread */ if (unlikely((t->flags & PF_KTHREAD) && (handler == SIG_KTHREAD_KERNEL) && !force)) return true; return sig_handler_ignored(handler, sig); } 

注释明确说明:

/* SIGKILL and SIGSTOP may not be sent to the global init */

sig_kernel_only在include/linux/signal.h中定义:

#define sigmask(sig) (1UL << ((sig) - 1)) #if SIGRTMIN > BITS_PER_LONG #define rt_sigmask(sig) (1ULL << ((sig)-1)) #else #define rt_sigmask(sig) sigmask(sig) #endif #define SIG_KERNEL_ONLY_MASK (\ rt_sigmask(SIGKILL) | rt_sigmask(SIGSTOP)) #define siginmask(sig, mask) \ ((sig) > 0 && (sig) < SIGRTMIN && (rt_sigmask(sig) & (mask))) #define sig_kernel_only(sig) siginmask(sig, SIG_KERNEL_ONLY_MASK)

x86下,SIGRTMIN在/arch/x86/include/uapi/asm/signal.h中定义:

#define SIGRTMIN 32

BITS_PER_LONG在/include/asm-generic/bitsperlong.h中定义,和具体架构有关:

#ifdef CONFIG_64BIT #define BITS_PER_LONG 64 #else #define BITS_PER_LONG 32 #endif /* CONFIG_64BIT */

Intel 32位下: 

 sig_kernel_only(sig) == siginmask(sig, SIG_KERNEL_ONLY_MASK) == siginmask(sig, (rt_sigmask(SIGKILL) | rt_sigmask(SIGSTOP))) == siginmask(sig, ((1ULL << ((SIGKILL)-1)) | (1ULL << ((SIGSTOP)-1)))) 

Read more

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

摘要:本文聚焦OpenClaw从测试环境走向生产环境的核心痛点,围绕“性能优化、安全加固、监控运维”三大维度展开实操讲解。先明确生产环境硬件/系统选型标准,再通过硬件层资源管控、模型调度策略、缓存优化等手段提升响应速度(实测响应效率提升50%+);接着从网络、权限、数据三层构建安全防护体系,集成火山引擎安全方案拦截高危操作;最后落地TenacitOS可视化监控与Prometheus告警体系,配套完整故障排查清单和虚拟实战案例。全文所有配置、代码均经实测验证,兼顾新手入门实操性和进阶读者的生产级部署需求,帮助开发者真正实现OpenClaw从“能用”到“放心用”的跨越。 优质专栏欢迎订阅! 【DeepSeek深度应用】【Python高阶开发:AI自动化与数据工程实战】【YOLOv11工业级实战】 【机器视觉:C# + HALCON】【大模型微调实战:平民级微调技术全解】 【人工智能之深度学习】【AI 赋能:Python 人工智能应用实战】【数字孪生与仿真技术实战指南】 【AI工程化落地与YOLOv8/v9实战】【C#工业上位机高级应用:高并发通信+性能优化】 【Java生产级避坑指南:

By Ne0inhk
ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

🎬 渡水无言:个人主页渡水无言 ❄专栏传送门: 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》 ❄专栏传送门: 《freertos专栏》《STM32 HAL库专栏》 ⭐️流水不争先,争的是滔滔不绝  📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生 | 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生 在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连 目录 前言  一、实验基础说明 1.1、互斥体简介 1.2 本次实验设计思路 二、硬件原理分析(看过之前博客的可以忽略) 三、实验程序编写 3.1 互斥体 LED 驱动代码(mutex.c) 3.2.1、设备结构体定义(28-39

By Ne0inhk
Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 后端工程师扔给你一个 Swagger (OpenAPI) 文档地址,你会怎么做? 1. 对着文档,手写 Dart Model 类(容易写错字段类型)。 2. 手写 Retrofit/Dio 的 API 接口定义(容易拼错 URL)。 3. 当后端修改了字段名,你对着报错修半天。 这是重复劳动的地狱。 swagger_dart_code_generator 可以将 Swagger (JSON/YAML) 文件直接转换为高质量的 Dart 代码,包括: * Model 类:支持 json_serializable,带 fromJson/

By Ne0inhk
Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

文章目录 * 前言 * make/makefile * 文件的三个时间 * Linux第一个小程序-进度条 * 回车和换行 * 缓冲区 * 程序的代码展示 * git指令 * 关于gitee * Linux调试器-gdb使用 * 作业部分 前言 做 Linux 开发时,你是不是也遇到过这些 “卡脖子” 时刻?写 makefile 时,明明语法没错却报错,最后发现是依赖方法行没加 Tab;想提交代码到 gitee,记不清 git add/commit/push 的 “三板斧”,还得反复搜教程;用 gdb 调试程序,输了命令没反应,才想起编译时没加-g生成 debug 版本;甚至连写个进度条,都搞不懂\r和\n的区别,导致进度条乱跳…… 其实这些问题,

By Ne0inhk