目录

引言:

一信号快速认识

1.1 生活角度的信号

1.1.1结论

1.2 技术应用角度的信号

1.2.2 一个系统函数(signal)

1.3信号概念

1.3.1查看信号

1.3.2 信号处理

• 忽略此信号:

• 执行该信号的默认处理动作:

自定义捕捉(Catch)一个信号:

结语:


引言:

在Linux系统中,进程之间的通信方式有很多种,而异步通信的核心机制之一,就是我们今天要聊的「信号」。对于刚接触Linux编程的小伙伴来说,信号可能显得有些抽象——它看不见、摸不着,却能在关键时刻“提醒”进程做出响应,比如我们常用的Ctrl+C终止程序,本质上就是一次信号触发。为了让大家轻松理解这个核心概念,我们不妨用生活中最常见的「快递通知」来类比,一步步拆解信号的产生、延迟处理和三种处理方式,再结合实际代码,看看如何通过signal()函数自定义信号处理逻辑,打破系统默认的操作行为,真正搞懂信号作为操作系统进程控制核心特性的作用。

一信号快速认识

1.1 生活角度的信号

• 你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能“识别快递”
• 当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快的行为并不是一定要立即执行,可以理解成“在合适的时候去取”。
• 在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你“记住了有一个快递要去取”
• 当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种:

1. 执行默认动作(幸福的打开快递,使用商品)

2. 执行自定义动作(快递是零食,你要送给你你的女友)
3. 忽略快递(快递拿上来之后,扔掉床头,继续开一把游戏)
• 快递到来的整个过程,对你来讲是异步的,你不能准确断定快递员什么时候给你打电话

1.1.1结论

• 你怎么能识别信号呢?识别信号是内置的,进程识别信号,是内核程序员写的内置特性。
• 信号产生之后,你知道怎么处理吗?知道。如果信号没有产生,你知道怎么处理信号吗?
知道。所以,信号的处理方法,在信号产生之前,已经准备好了。

• 处理信号,立即处理吗?我可能正在做优先级更高的事情,不会立即处理?什么时候?
适的时候。进程能临时保存信号。

• 怎么进行信号处理啊?a.默认 b.忽略 c.自定义, 后续都叫做信号捕捉。

1.2 技术应用角度的信号

// sig.cc
#include <iostream>
#include <unistd.h>
int main()
{
    while(true){
        std::cout << "I am a process, I am waiting signal!" << std::endl;
        sleep(1);
    }
}

• 用户输入命令,在Shell下启动一个前台进程
• 用户按下Ctrl+C ,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进

• 前台进程因为收到信号,进而引起进程退出

1.2.2 一个系统函数(signal)

在 Linux 和 C 语言编程中,signal 函数是处理系统信号(Signal)最基础、最常用的接口。简单来说,它允许你的程序“拦截”特定的系统事件(比如用户按下 Ctrl+C),并执行你自己定义的操作,而不是按照系统的默认行为(比如直接强制退出程序)。

1. 函数原型

#include <iostream>
#include <unistd.h>
#include <signal.h>
void handler(int signumber)
{
    std::cout << "我是: " << getpid() << ", 我获得了一个信号: " << signumber <<
    std::endl;
}
int main()
{
    std::cout << "我是进程: " << getpid() << std::endl;
    signal(SIGINT/*2*/, handler);
    while(true){
        std::cout << "I am a process, I am waiting signal!" << std::endl;
        sleep(1);
    }
}
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • sig:你要处理的那个信号的编号(比如 SIGINT)。
  • handler:当信号发生时,你希望程序去执行的“自定义函数”。(handler是一个函数指针)

其实, Ctrl+C 的本质是向前台进程发送SIGINT 即2 号信号,我们证明一下

#include <iostream>
#include <unistd.h>
#include <signal.h>
void handler(int signumber)
{
    std::cout << "我是: " << getpid() << ", 我获得了一个信号: " << signumber <<
    std::endl;
}
int main()
{
    std::cout << "我是进程: " << getpid() << std::endl;
    signal(SIGINT/*2*/, handler);
    while(true){
        std::cout << "I am a process, I am waiting signal!" << std::endl;
        sleep(1);
    }
}

• 这里进程为什么不退出?

默认捕捉是终止进程,进行了自定义捕捉。
• 这个例子能说明哪些问题?

信号处理,是进程自己处理

注意:

• 要注意的是,signal函数仅仅是设置了特定信号的捕捉行为处理方式,并不是直接调用处
理动作。
如果后续特定信号没有产生,设置的捕捉函数永远也不会被调用!!
Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个&可以放到后台运行,这样
Shell不必等待进程结束就可以接受新的命令,启动新的进程。
• Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像Ctrl-C
这种控制键产生的信号。

• 前台进程在运行过程中用户随时可能按下Ctrl-C 而产生一个信号,也就是说该进程的用
户空间代码执行到任何地方都有可能收到SIGINT 信号而终止,所以信号相对于进程的控
制流程来说是异步(Asynchronous)的。

1.3信号概念

信号是进程之间事件异步通知的一种方式,属于软中断。

1.3.1查看信号

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到

编号34以上的是实时信号,不讨论实时信号。这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明: man 7 signal

1.3.2 信号处理

可选的处理动作有以下三种:

• 忽略此信号:
#include <signal.h>
#include <unistd.h>
#include <signal.h>
void handler(int signumber)
{
    std::cout << "我是: " << getpid() << ", 我获得了一个信号: " << signumber
    << std::endl;
}
int main()
{    
    std::cout << "我是进程: " << getpid() << std::endl;
    signal(SIGINT/*2*/, SIG_IGN); // 设置忽略信号的宏
    while(true){
        std::cout << "I am a process, I am waiting signal!" << std::endl;
        sleep(1);
    }
}

输入ctrl+c毫无反应

• 执行该信号的默认处理动作:
#include <iostream>
#include <unistd.h>
#include <signal.h>
void handler(int signumber)
{
    std::cout << "我是: " << getpid() << ", 我获得了一个信号: " << signumber
    << std::endl;
}
int main()
{
    std::cout << "我是进程: " << getpid() << std::endl;
    signal(SIGINT/*2*/, SIG_DFL);
    while(true){
        std::cout << "I am a process, I am waiting signal!" << std::endl;
        sleep(1);
    }
}

正常退出。

自定义捕捉(Catch)一个信号:
#include <iostream>
#include <unistd.h>
#include <signal.h>
void handler(int signumber)
{
    std::cout << "我是: " << getpid() << ", 我获得了一个信号: " << signumber <<
    std::endl;
}
int main()
{
    std::cout << "我是进程: " << getpid() << std::endl;
    signal(SIGINT/*2*/, handler);
    while(true){
        std::cout << "I am a process, I am waiting signal!" << std::endl;
        sleep(1);
    }
}

结语:

看到这里,相信你已经对Linux信号机制有了清晰的认知——它不像我们写代码时的同步调用那样“即时响应”,更像是一份灵活的“快递通知”,进程可以根据自身状态决定何时处理、如何处理。从信号的产生到自定义捕获,从SIGINT信号的实际应用到signal()函数的实战演示,我们用通俗的类比+实用的代码,拆解了这个操作系统中的关键知识点。信号机制是Linux进程控制的基础,也是后续学习进程管理、异常处理的重要铺垫,希望这篇文章能帮你打通思路,在实际编程中灵活运用信号,解决更多进程通信的问题。如果觉得有收获,欢迎点赞收藏,也可以在评论区分享你在使用信号时遇到的小问题,我们一起交流进步~

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐