Linux信号三部曲:产生机制、处理方式与内核接口

Linux信号三部曲:产生机制、处理方式与内核接口

Linux系列


文章目录


前言

Linux中,信号(Signal)是一种进程间通信(IPC)的机制,它用来通知进程发生了特定的事件。进程接受到信号后会根据信号的类型结合自己的处理方式做出相应的处理。


一、背景知识铺垫

1.1 信号的基本概念

Linux信号是一种异步通信机制,用于在进程之间传递事件或在系统于进程之间进行交互。当发生某个特定事件时,如:用户输入特定组合建(Ctrl+c等)、进程异常终止,系统就会向相关进程发送信号。

1.2 进程对信号的处理

进程在被设计时,就内置了识别信号的方法以及默认处理不同信号方式,当进程接收到信号时,并不一定会立即处理,这也就要求进程需要具有保存信号的能力,当等到合适的时候,进程会根据信号的类型结合自己的处理方式法,做出处理。
进程在处理信号的方式:

  • 默认处理方式(进程内置的)
  • 忽略信号
  • 自定义处理方式(捕捉信号后,使用用户设定的方法)

二、信号的产生

穿插了一部分拓展知识

2.1 前台进程和后台进程

首先我们先来看下面这个程序:

#include<iostream>#include<unistd.h> using namespace std;intmain(){while(true){ cout<<"I'm a crazy process,PID:"<<getpid()<<endl;sleep(1);}return0;}
在这里插入图片描述


当我们执行该程序后,再输入ls、pwd,可以看到指令并不会执行,进程则一直运行,当我们使用Ctrl+c就可以将进程终止,这样的进程就是前台进程。再次执行该程序:

在这里插入图片描述


这次我们以后台进程的形式执行该进程./可执行程序 &,可以发现,当进程执行后,我们再输入指令,此时指令是可以成功执行的,当我们使用Ctrl+c时无法终止进程。这种进程为后台进程。

Linux中,一次登录,一个终端,一般配有一个bash,而每个终端只允许有一个前台进程,可有多个后台进程,当我们执行./process时,前台进程就由bash变为了./process而键盘输入是优先被前台进程获取的,所以指令无法被执行,但前台进程./peocess接收到Ctrl+c信号时就会终止。这样我们再来理解第二个现象,当我们以后台进程运行./process是,此时bash仍被视为前台进程,当用户输入指令是仍可被接收并执行,此时再输入Ctrl+c信号./process进程并没有接收,所以没有终止。

前台进程:会独占终端,直到进程执行完成或者被挂起,在这期间终端无法接受其他命令输入,用户只能与该进程进行交互。
后台进程:不会占用终端,终端可以继续接受用户输入的其他命令,用户可以在同一个终端中同时启动多个后台进程。
前台进程:其执行过程会受到用户操作的直接影响,比如用户可以通过键盘输入来中断或暂停进程。如果终端关闭,前台进程通常会被终止,除非进行了特殊的设置。
后台进程:通常是长时间运行的,不受终端关闭的影响,除非明确地对其进行停止或重启操作。它按照自身的逻辑和任务需求在后台持续运行,不会因为用户的一些常规操作而中断

Ctrl+c本质会解释为2号信号,后面我们会验证

2.2 键盘组合键

这是我们在学习Linux过程中,比较常用的一种向进程发送信号的方式,它通过一些特定的键盘组合键,来发送一些特殊的信号,如Ctrl+c终止进程。组合有很多种,都比较简单,这里我们想要介绍的是,Ctrl+c这类组合键是如何被转换为信号,又是如何被进程接收的?

我们可以确定的意见事是,CPU不能直接从键盘读取数据(冯诺依曼体系结构),那么这个工作只能交由操作系统来完成,操作系统又是如何得知键盘有数据了呢?我们根据下图来回答:

在这里插入图片描述

CPU上有很多针脚,每个针脚对应一个硬件设备(键盘、网卡),当用户按下Ctrl+c组合键时,键盘发生硬件中断,产生中断号,通过对于针脚发送(充放电)给CPU,通知CPU进行相关处理,操作系统从CPU读取到中断号,通过中断号在自己的中断向量表中索引到对应方法地址,执行该方法(读取键盘),操作系统识别如果是数据直接读取,如果是组合键就将他解释成对应信号,如ctrl+c解释为2号信号,并将它读入键盘缓冲区(一切皆文件),再拷贝至用户缓冲区,被进程接收,进程执行对应处理方法。我们的信号处理方式(异步通知、事件驱动等)就是模拟的硬件中断,因此信号又被叫做软件中断。

2.3 kill 命令

对于上面的后台进程,我们可以通过kill指令的形式,给它发送信号,终止它,可以通过kill -l查看信号种类:

在这里插入图片描述


0~31为普通信号,34~64为实时信号(我们不研究),这里有多种信号来都能达到终止,后台进程的要求,如:2号终止进程,9号杀掉进程。
使用格式:kill -信号编号 进程PID.

常用信号

信号编号信号名称触发方式作用
2SIGINTCtrl+C终止前台进程
3SIGQUITCtrl+\终止进程并生成core文件
9SIGKILLkill -9 pid强制终止进程,不可被捕获或忽略
15SIGTERMkill -15 pid正常终止进程,进程可捕获并进行清理
1SIGHUP终端连接断开等让进程重新初始化或终止
18SIGCONTkill -18 pid继续执行暂停的进程
19SIGSTOPkill -19 pid暂停进程,不可被捕获或忽略
20SIGTSTPCtrl+Z暂停前台进程,将其放入后台
10SIGUSR1kill -10 pid用户自定义信号,用于特定程序逻辑
12SIGUSR2kill -12 pid用户自定义信号,用于特定程序逻辑

2.4 系统调用

2.4.1 signal()接口

#include<signal.h>typedefvoid(*sighandler_t)(int);sighandler_tsignal(int signum,sighandler_t handler);

功能:捕捉指定信号,执行自定义功能

参数

  1. signum: 要捕捉信号编号
  2. handler:函数指针,用互自定义的方法

下面的程序我们使用signal函数捕捉二号信号,执行我们自定义的方法。

#include<iostream>#include<unistd.h>#include<signal.h> using namespace std;voidhandler(int num){ cout<<"I captured Signal No."<<num<<endl;return;}intmain(){sighandler_t _handler=signal(2,handler);while(true){ cout<<"I'm a crazy process,PID:"<<getpid()<<endl;sleep(1);}return0;}
在这里插入图片描述


可以看到此时我们再使用Ctrl+c,信号程序就不会终止,而是执行我们的自定义方法。上面的场景也完美的呈现了,信号的异步性(程序先调用singnal,但是此时进程没有收到信号,所以这个函数不会执行,当进程接收到信号后,就会执行对于方法)。
需要注意的是,并不是所有信号,都可以被捕捉的,我们可以通过下面的方式来验证:

#include<iostream>#include<unistd.h>#include<signal.h> using namespace std;voidhandler(int num){ cout<<"I captured Signal No."<<num<<endl;return;}intmain(){for(int i=0;i<=64;i++)sighandler_t _handler=signal(i,handler);while(true){ cout<<"I'm a crazy process,PID:"<<getpid()<<endl;sleep(1);}return0;}

执行这个进程时,通过kill指令向进程发信号,这里不方便演示,大家可以自己尝试(9号和19号好像不能被捕捉)。

2.4.2 kill()接口

#include<sys/types.h>#include<signal.h>intkill(pid_t pid,int sig);

功能:向指定进程发送信号

参数

  1. pid:接收信号的进程PID
  2. sig:信号编号

返回值
执行成功返回零,失败饭回-1并设置errno

示例:

#include<iostream>#include<signal.h>#include<sys/types.h> using namespace std;intmain(int argc,char*argv[]){if(argc!=3){ cout <<"Usage:\n\t"<< argv[0]<<" signum pid\n\n";exit(1);}int signum=stoi(argv[1]);int pid=stoi(argv[2]);int n=kill(pid,signum);if(n==-1){perror("kill");exit(1);}return0;}

现在我们就可以使用上面这个进程,来对其他进程发送信号了。

在这里插入图片描述


当然你可以让你的进程直接使用kill自己给自己发信号。

2.4.3 raise()接口

#include<signal.h>intraise(int sig);

功能:让当前进程给自己发送信号

参数

  • sig:信号编号
#include<iostream>#include<unistd.h>#include<signal.h> using namespace std;voidhandler(int num){ cout<<"I captured Signal No."<<num<<endl;return;}intmain(){sighandler_t _handler=signal(2,handler);int cnt=5;while(true){ cout<<"I'm a crazy process,PID:"<<getpid()<<endl; cnt--;if(cnt==0)break;//跳出循环sleep(1);}raise(9); cout<<"111111"<<endl;return0;
在这里插入图片描述


循环执行五次后跳出,执行raise()完成“自杀”,程序终止,我们可以感受到raise()其实就是分装的kill()

2.4.4 abort()接口

#include<stdlib.h>voidabort(void);

功能:向当前进程发送SIGABRT(6号)信号,默认情况下,进程收到该信号后会立即终止,即使被用户捕获,在执行过用户提供的方法后依然终止进程。

#include<iostream>#include<unistd.h>#include<signal.h> using namespace std;voidhandler(int num){ cout<<"I captured Signal No."<<num<<endl;return;}intmain(){sighandler_t _handler=signal(6,handler);while(true){ cout<<"I'm a crazy process,PID:"<<getpid()<<endl;abort();}return0;}
在这里插入图片描述
如果不调用该函数,使用指令发送六号信号,当信号被捕获后,进程不会终止,abrot函数内部做了特殊处理,才会使进程终止,你可以测试看看。

总结

特点:
异步传递(随时可能中断进程)

  • SIGINT(2):由键盘输入Ctrl+C产生,用于中断正在运行的进程。
  • SIGKILL(9):强制终止进程,不能被捕获和忽略,用于紧急情况下终止进程。
  • SIGSTOP(19):暂停进程,不能被捕获和忽略,可使用SIGCONT信号恢复进程运行

不可捕获的信号

SIGKILL(9)和SIGSTOP(19)无法被捕获、阻塞或忽略,用于强制控制进程。
信号发送函数

  • kill函数:可以向指定进程发送指定信号,例如 kill(pid, SIGINT) 向进程号为 pid 的进程发送 SIGINT 信号。
  • raise函数:用于向当前进程发送信号,如 raise(SIGABRT) 向当前进程发送 SIGABRT 信号。

处理方式:

  • 默认处理:系统为每个信号定义了默认的处理行为,如终止进程、产生核心转储、忽略信号等。
  • 捕获信号:进程可以通过 signal 函数当接收到指定信号时,执行自定义的处理逻辑。
  • 忽略信号:进程可以使用 signal 函数将信号设置为忽略,使进程不响应该信号,但有些信号(如 SIGKILL 和 SIGSTOP )不能被忽略(这点我在下篇介绍)。

Read more

AI 直接生成前端代码:我的软件原型设计流,从此告别重复画图

AI 直接生成前端代码:我的软件原型设计流,从此告别重复画图

近年来,AI 辅助开发越来越成熟,尤其是在快速原型设计方面。今天分享一下我如何借助 Cursor、Trace solo、ChatGPT、Qoder 等 AI 工具,高效完成软件原型的自动绘制与代码生成。 📌 核心流程三步走 1️⃣ 用 AI 输出需求文档(非技术描述) 首先,我会让 AI 根据产品思路或功能描述,生成一份清晰、无技术细节的需求文档。这一步不写代码,只聚焦逻辑与用户流程。 2️⃣ AI 生成 HTML 原型代码 基于上一步的需求文档,直接让 AI 生成对应的 HTML 代码,快速搭建出可交互的前端原型。支持实时预览,直观看到界面效果。 3️⃣ 反复微调,直至满意 生成的原型往往需要多次调整。通过自然语言描述修改方向,AI 可快速迭代代码,直至达到想要的交互与视觉效果。

By Ne0inhk
WebAgent详解+实战:用开源AI智能体搞定产品与竞品市场调研

WebAgent详解+实战:用开源AI智能体搞定产品与竞品市场调研

在市场调研场景中,产品及竞品分析往往需要投入大量人力,手动浏览网页、提取信息、整理数据,不仅效率低下,还容易出现信息遗漏、误差等问题。WebAgent作为通义实验室开源的端到端自主网页智能体,凭借强大的中文语义理解、多步骤推理和结构化输出能力,可完全本地部署且永久免费,能高效替代人工完成网页信息采集、竞品数据提取、产品信息汇总等调研工作。本文将从WebAgent核心介绍、部署要点入手,聚焦产品与竞争对手调研场景,一步步实现实战示例,让无论是开发者还是市场从业者,都能快速上手,用AI提升调研效率,摆脱重复劳动。 一、初识WebAgent:阿里开源的网页智能体“神器” 1.1 什么是WebAgent? WebAgent是阿里巴巴通义实验室开源的自主网页智能体框架,核心定位是“模拟人类浏览网页的完整流程”,能理解自然语言指令、规划浏览路径、执行网页操作(点击、翻页、搜索等)、提取关键信息并结构化输出,无需人工干预即可完成复杂的网页相关任务。 与国外的AgentQL相比,WebAgent最大的优势的是完全开源免费、支持本地部署、中文语义优化,无需调用云端API,数据可完全保存在内网,

By Ne0inhk

前端转型AI的“第一公里”:如何建立正确的AI心智模型?

前端转型AI的“第一公里”:如何建立正确的AI心智模型? 在过去的一年里,我见证了太多前端同行的焦虑与迷茫。AI浪潮袭来,很多人匆忙上阵,学会了调用OpenAI的API,甚至跑通了LangChain的Demo,但在实际落地时却频频踩坑。 我们习惯了确定性的世界:输入1 + 1,输出必然是2;写了display: flex,布局必然改变。然而,AI开发是一个概率性的世界:同样的Prompt,两次调用可能得到截然不同的结果。这种底层逻辑的冲突,是前端转型AI最大的“拦路虎”。 很多前端工程师把大模型仅仅当成一个“智能API接口”,试图用传统的硬编码逻辑去控制它,结果往往是Prompt越写越长,系统却越来越不稳定。这并非技术能力不足,而是心智模型尚未完成迁移。 从“函数思维”到“上下文思维” 传统前端开发的核心是“函数思维”:我们定义输入、处理逻辑和输出,追求的是精准控制。但在AI应用开发中,这种思维必须升级为“上下文思维”。 大模型本质上是一个“概率预测机”。它不像函数那样执行指令,而是像人一样理解语境。前端开发者转型AI的第一步,不是去学Python深度学习框架,而是学会如

By Ne0inhk