Linux进程信号——像收快递一样理解 Linux 信号

目录

引言:

一信号快速认识

[1.1 生活角度的信号](#1.1 生活角度的信号)

1.1.1结论

[1.2 技术应用角度的信号](#1.2 技术应用角度的信号)

[1.2.2 一个系统函数(signal)](#1.2.2 一个系统函数(signal))

1.3信号概念

1.3.1查看信号

[1.3.2 信号处理](#1.3.2 信号处理)

[• 忽略此信号:](#• 忽略此信号:)

[• 执行该信号的默认处理动作:](#• 执行该信号的默认处理动作:)

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

结语:


引言:

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

一信号快速认识

1.1 生活角度的信号

• 你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能"识别快递"

• 当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快的行为并不是一定要立即执行,可以理解成"在合适的时候去取"。

• 在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你"记住了有一个快递要去取"

• 当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种:

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

2. 执行自定义动作 (快递是零食,你要送给你你的女友)
3. 忽略快递 (快递拿上来之后,扔掉床头,继续开一把游戏)

• 快递到来的整个过程,对你来讲是异步的,你不能准确断定快递员什么时候给你打电话

1.1.1结论

• 你怎么能识别信号呢?识别信号是内置的,进程识别信号,是内核程序员写的内置特性。

• 信号产生之后,你知道怎么处理吗?知道。如果信号没有产生,你知道怎么处理信号吗?

知道。所以,信号的处理方法,在信号产生之前,已经准备好了。

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

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

1.2 技术应用角度的信号

cpp 复制代码
// 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. 函数原型

cpp 复制代码
#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);
    }
}
cpp 复制代码
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • sig:你要处理的那个信号的编号(比如 SIGINT)。
  • handler:当信号发生时,你希望程序去执行的"自定义函数"。(handler是一个函数指针)

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

cpp 复制代码
#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 信号处理

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

• 忽略此信号:
cpp 复制代码
#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毫无反应

• 执行该信号的默认处理动作:
cpp 复制代码
#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)一个信号:
cpp 复制代码
#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进程控制的基础,也是后续学习进程管理、异常处理的重要铺垫,希望这篇文章能帮你打通思路,在实际编程中灵活运用信号,解决更多进程通信的问题。如果觉得有收获,欢迎点赞收藏,也可以在评论区分享你在使用信号时遇到的小问题,我们一起交流进步~

相关推荐
灰色人生qwer1 小时前
Python 规则:带默认值的参数必须放在不带默认值的后面
linux·windows·python
‎ദ്ദിᵔ.˛.ᵔ₎1 小时前
c++ 11左值和右值
c++
foundbug9991 小时前
MATLAB实现:基于图像对比度和波段相关性的高光谱波段选择算法
开发语言·算法·matlab
Hical_W1 小时前
C++ Web 框架性能实测(Benchmark)
c++·开源
嘿嘿嘿x31 小时前
Linux-实践
linux·运维·算法
czt_java2 小时前
线程安全问题
java·开发语言·jvm
techdashen2 小时前
Rust 模块和文件不是一回事:一次讲清 `mod`、`use`、`pub use`
开发语言·后端·rust
Wy_编程2 小时前
go中的协程Goroutine
开发语言·golang
lzh200409192 小时前
手撕线程池:巩固Linux线程知识
linux·c++