
Linux学习笔记:
https://blog.csdn.net/2301_80220607/category_12805278.html?spm=1001.2014.3001.5482
前言:
在前面我们已经学习了有关信号的一些基本的知识点,包括:信号的概念、信号产生和信号处理等,今天我们重点来讲解一下信号在内核中的处理以及信号捕捉的相关知识点 在这篇文章中,我们将深入探讨 Linux 信号在内核中的处理流程,详细讲解信号递达、信号阻塞、未决信号、信号集操作、信号捕捉等内容,并通过大量的代码示例和实际场景来展示信号如何在 Linux 中运作。
与信号有关的还有一个很重要的知识点是有关用户态、内核态和状态切换的知识,本篇没有进行讲解,需要自己再去了解一下
信号是由内核或其他进程通过系统调用发送给目标进程的。当进程正在执行时,信号能够在不干扰进程当前操作的情况下打断它的执行,触发某种特定的行为。信号的处理流程在 Linux 内核中被设计得非常灵活,既支持异步信号处理,又能通过进程的信号屏蔽机制来控制信号的递达。
信号在内核中的表示示意图:

信号递达是信号机制中的核心概念,它是信号从信号源发送到目标进程的过程。信号递达的实现依赖于内核的进程调度机制。在进程执行过程中,内核需要判断该进程是否有需要处理的未决信号,信号的递达会在进程的上下文切换时被触发。
信号的递达取决于以下几个因素:
SIGRTMIN 开始)通常会比标准信号更快地递达,并且能够提供更多的控制选项。信号的递达过程通常包括以下几个步骤:
kill() 函数发送。#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void signal_handler(int sig) {
printf("Received signal: %d\n", sig);
}
int main() {
signal(SIGINT, signal_handler); // 捕捉 SIGINT 信号
printf("Waiting for SIGINT...\n");
while(1) {
sleep(1); // 进入等待状态,直到接收到信号
}
return 0;
}在上面的代码中,进程会一直运行并等待 SIGINT 信号(通常由按下 Ctrl+C 触发)。一旦进程接收到 SIGINT 信号,内核会将其递送到进程,并触发信号处理函数 signal_handler

当信号发送给进程时,如果该信号被进程的信号屏蔽字阻塞,那么该信号就会进入未决状态。未决信号是那些已经被发送但尚未被递达的信号。内核维护了每个进程的未决信号队列,并会在进程解除对该信号的阻塞时按顺序递送这些信号。
在 Linux 内核中,每个进程都有一个 task_struct 结构体,其中包含了当前进程的未决信号集合。每当一个信号发送给一个进程时,如果该信号被阻塞,内核不会立即递送它,而是将其存放在进程的未决信号队列中,直到进程解除对该信号的阻塞。
未决信号通常在进程解除信号屏蔽字后,由内核递送。递送顺序通常与信号发送顺序一致,且会按照优先级递送实时信号和标准信号。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main() {
sigset_t pending;
sigpending(&pending); // 获取当前进程的未决信号
if (sigismember(&pending, SIGINT)) {
printf("SIGINT is pending.\n");
} else {
printf("No SIGINT pending.\n");
}
sleep(10); // 稍作停顿,方便查看信号状态
return 0;
}通过使用 sigpending() 函数,我们可以查看当前进程的未决信号集。如果进程没有处理 SIGINT 信号,且信号被阻塞,则该信号会处于未决状态。

sigset_t信号集(sigset_t)是一个用于表示信号集合的数据结构,它通过位掩码的方式表示进程当前可以接受的信号集合。sigset_t 通常是一个整数或更大的数据类型,每一位对应一个信号。
在 Linux 中,常用的信号集操作函数包括:
sigemptyset():初始化信号集为空集。sigaddset():将某个信号添加到信号集中。sigdelset():将某个信号从信号集中删除。sigismember():判断某个信号是否在信号集中。#include <signal.h>
#include <stdio.h>
int main() {
sigset_t set;
sigemptyset(&set); // 初始化为空集
sigaddset(&set, SIGINT); // 将 SIGINT 添加到信号集中
sigaddset(&set, SIGTERM); // 将 SIGTERM 添加到信号集中
if (sigismember(&set, SIGINT)) {
printf("SIGINT is in the set.\n");
}
sigdelset(&set, SIGINT); // 从信号集中删除 SIGINT
if (!sigismember(&set, SIGINT)) {
printf("SIGINT is no longer in the set.\n");
}
return 0;
}
sigprocmasksigprocmask() 是一个用于修改进程信号屏蔽字的系统调用,它可以用来阻塞、解除阻塞或查询进程的信号屏蔽字。信号屏蔽字定义了哪些信号是被阻塞的,从而影响信号递达的时机。
sigprocmask() 具有以下操作模式:
SIG_BLOCK:将指定的信号添加到信号屏蔽字中,阻塞这些信号。SIG_UNBLOCK:从信号屏蔽字中删除指定信号,解除阻塞。SIG_SETMASK:将信号屏蔽字设置为指定值,替换当前的信号屏蔽字。sigprocmask() 阻塞和解除阻塞信号#include <signal.h>
#include <stdio.h>
#include<unistd.h>
int main() {
sigset_t new_mask, old_mask;
sigemptyset(&new_mask);
sigaddset(&new_mask, SIGINT); // 阻塞 SIGINT
sigprocmask(SIG_BLOCK, &new_mask, &old_mask); // 阻塞 SIGINT
// 信号屏蔽后,可以进行一些操作
printf("SIGINT is blocked. Press Ctrl+C to send SIGINT.\n");
sleep(10); // 暂停,等待 Ctrl+C 输入
// 恢复信号屏蔽字
sigprocmask(SIG_SETMASK, &old_mask, NULL); // 恢复原信号屏蔽字
printf("SIGINT is unblocked.\n");
sleep(10); // 等待信号递达
return 0;
}在这段代码中,SIGINT 信号在前 10 秒内被阻塞,用户按下 Ctrl+C 时信号不会立即递达。10 秒后,信号屏蔽被解除,SIGINT 信号会被递送并触发相应的处理。

sigpending()sigpending() 函数用于获取当前进程的未决信号,它返回一个信号集,表示该进程尚未处理的信号集合。sigpending() 的实现依赖于进程的信号队列,它可以用于调试和监控进程的信号处理状态。
sigpending() 查看未决信号#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int main() {
sigset_t pending;
sigemptyset(&pending);
// 阻塞 SIGINT 信号
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigprocmask(SIG_BLOCK, &mask, NULL);
// 发送 SIGINT 信号,但它会被阻塞
kill(getpid(), SIGINT);
// 获取未决信号
sigpending(&pending);
if (sigismember(&pending, SIGINT)) {
printf("SIGINT is pending.\n");
} else {
printf("No SIGINT pending.\n");
}
sleep(5); // 稍作等待,防止信号丢失
return 0;
}此程序模拟了阻塞 SIGINT 信号并通过 sigpending() 查看进程的未决信号状态。如果信号被阻塞,它将在信号屏蔽字解除后递达。
信号捕捉是指进程通过自定义信号处理函数来响应特定的信号。Linux 提供了 signal() 和 sigaction() 两种方式来捕捉信号。signal() 是一种简单的接口,而 sigaction() 提供了更为复杂的配置选项,使得开发者能够在处理信号时获得更多的控制权。
signal() 捕捉信号signal() 是最基础的信号捕捉方式,它允许开发者指定一个信号处理函数来响应特定信号。signal() 的使用非常简单,但它并不支持所有高级功能,如信号的重入处理或复杂的信号控制。
signal() 捕捉信号#include <signal.h>
#include <stdio.h>
#include<unistd.h>
void signal_handler(int sig) {
printf("Received signal: %d\n", sig);
}
int main() {
signal(SIGINT, signal_handler); // 捕捉 SIGINT 信号
printf("Waiting for SIGINT...\n");
while (1) {
sleep(1); // 程序将一直运行,直到接收到信号
}
return 0;
}在这个示例中,signal_handler 函数会在接收到 SIGINT 信号时被调用。

sigaction() 捕捉信号sigaction() 提供了比 signal() 更灵活的方式来处理信号。它允许开发者在捕捉信号时设定更多的参数,比如如何处理重入信号、是否需要恢复默认行为等。
sigaction() 的结构体定义如下:
struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};sa_handler:指定信号处理函数。sa_mask:指定在信号处理期间需要阻塞的信号集。sa_flags:设定信号处理的行为。sigaction() 捕捉信号#include <signal.h>
#include <stdio.h>
#include<unistd.h>
void signal_handler(int sig) {
printf("Received signal: %d\n", sig);
}
int main() {
struct sigaction sa;
sa.sa_handler = signal_handler; // 指定信号处理函数
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask); // 初始化信号集
sigaction(SIGINT, &sa, NULL); // 捕捉 SIGINT 信号
printf("Waiting for SIGINT...\n");
while (1) {
sleep(1); // 程序将一直运行,直到接收到信号
}
return 0;
}通过 sigaction(),程序能够灵活地处理信号,并控制信号捕捉的行为,甚至允许在处理信号时阻塞其他信号。

本文我们讲解了信号的处理机制,并且对信号捕捉进行了更详细的补充,结合上篇内容,基本上将信号部分的内容进行了大概的讲解,认真看一下相信会对你有所帮助