信号阻塞与捕捉

信号阻塞与捕捉

  • 1. 信号的阻塞
    • 1.1 Block(屏蔽)
    • 1.2 Pending(待处理)
    • 1.3 Handler(处理函数)
  • 2. 信号集操作函数
    • 2.1 sigset_t
    • 2.2 操作函数
    • 2.3 sigprocmask函数
    • 2.4 sigpending函数
  • 3. 信号的捕捉
  • 4. sigaction函数

1. 信号的阻塞

在Linux中,BlockPendingHandler 是与信号处理相关的三个重要概念,它们分别描述了信号的屏蔽(Block)、等待处理(Pending)和相应处理函数(Handler) 的情况。

信号阻塞与捕捉_第1张图片

1.1 Block(屏蔽)

概念: Block 表示对某个信号进行屏蔽,使其在进程中暂时无法被接收。

组成: 信号屏蔽集(Signal Mask)是由一组位表示的数据结构每一位对应一个信号。当某个信号的位被设置(为1)时,表示该信号被屏蔽;当被清零(为0)时,表示该信号不被屏蔽。进程可以通过修改信号屏蔽集来控制屏蔽和解除屏蔽的信号。

1.2 Pending(待处理)

概念: Pending 表示已经发送给进程但尚未被处理的信号

组成: 待处理信号集(Pending Signal Set)是一个由一组位表示的数据结构,类似于信号屏蔽集。当某个信号的位被设置时(为1),表示该信号当前在待处理状态;清零(为0)则表示该信号当前不在待处理状态。进程可以通过系统调用获取当前待处理信号的集合。

1.3 Handler(处理函数)

概念: Handler 表示与信号相关联的处理函数,用于在接收到信号时执行特定的操作。

组成: 为了处理各种信号,内核维护了一个信号处理函数表。这个表以信号编号为索引,对应的元素是与该信号关联的处理函数的地址。每个处理函数都是用户定义的,用于在接收到相应信号时执行特定的操作

2. 信号集操作函数

2.1 sigset_t

sigset_t 是一个数据结构,用于表示信号集合,它在 C 语言中通常是一个位图或类似的数据结构。该数据结构的目的是管理进程中各种信号的状态,包括哪些信号被阻塞、哪些信号处于未决状态等

通常来说,sigset_t 是一个包含足够位数的整数类型,每个位代表一个信号。
位的值表示信号的状态,例如,1 表示信号被阻塞或处于未决状态,0 表示信号未被阻塞或未处于未决状态

2.2 操作函数

#include
int sigemptyset(sigset_t *set);

功能: 初始化指定的信号集,将其中所有信号的对应位清零
参数set 是指向 sigset_t 类型的指针,表示待初始化的信号集。
返回值: 成功返回 0,失败返回 -1。

#include
int sigfillset(sigset_t *set);

功能: 初始化指定的信号集,将其中所有信号的对应位置位,表示该信号集包含系统支持的所有信号。
参数set 是指向 sigset_t 类型的指针,表示待初始化的信号集。
返回值: 成功返回 0,失败返回 -1。

#include
int sigaddset(sigset_t *set, int signo);

功能: 将指定的信号 signo 添加到信号集 set 中,将该信号的对应位置为 1,表示该信号现在处于“有效”状态。。
参数set 是指向 sigset_t 类型的指针,表示待修改的信号集;
    signo 是待添加的信号。
返回值: 成功返回 0,失败返回 -1。

#include
int sigdelset(sigset_t *set, int signo);

功能: 从指定的信号集中删除某个有效信号
参数set 是指向 sigset_t 类型的指针,表示待修改的信号集;
    signo 是待删除的信号。
返回值: 成功返回 0,失败返回 -1。

#include
int sigismember(const sigset_t *set, int signo);

功能: 判断指定的信号是否是信号集的成员。
参数set 是指向 sigset_t 类型的指针,表示待查询的信号集;
    signo 是待检查的信号。
返回值: 若成功,包含则返回 1,不包含则返回 0;失败返回 -1。

2.3 sigprocmask函数

sigprocmask 函数允许程序员读取或更改进程的信号屏蔽字,从而控制信号的阻塞状态。同时,sigpending 函数允许了解那些在进程中产生但被阻塞的信号

在这里插入图片描述

输入型参数:
how操作方式:
SIG_BLOCK:将 set 中的信号添加到当前的信号屏蔽字中。
相当于 mask = mask | set,即将 set 中的信号添加到当前的信号屏蔽字中

SIG_UNBLOCK:从当前的信号屏蔽字中解除 set 中的信号。
相当于 mask = mask & ~set,即将 set 中的信号从当前的信号屏蔽字中解除

SIG_SETMASK:将当前的信号屏蔽字设置为 set 中的值。
相当于 mask = set,即将当前的信号屏蔽字设置为 set 中指定的值
set参数:
set 参数是一个指向 sigset_t 类型的指针,用于指定要进行信号操作的信号集
输出型参数:
oset 参数是一个指向 sigset_t 类型的指针,用于存储原始的信号屏蔽字
返回值:
sigprocmask 函数的返回值表示操作是否成功。如果成功,返回 0;如果出错,返回 -1。

2.4 sigpending函数

sigpending 函数获取的就是当前进程的 pending
在这里插入图片描述

set参数:
set 参数是一个指向 sigset_t 类型的指针,用于存储当前被阻塞但仍处于未决状态的信号

代码示例

#include 
#include 
#include 

using namespace std;

// 打印信号集合的函数
void PrintPending(sigset_t &pending)
{
    // 逐个检查信号位,打印二进制表示
    for (int signo = 31; signo >= 1; signo--)
        if (sigismember(&pending, signo))
            cout << "1";
        else
            cout << "0";
    cout << "\n";
}

// 信号处理函数
void handler(int signo)
{
    // 输出捕获到的信号
    cout << "捕获到信号: " << signo << endl;
}

int main()
{
    // 初始化信号集
    sigset_t bset, oset;
    sigemptyset(&bset);
    sigemptyset(&oset);

    // 将所有信号都添加到信号集中,实现屏蔽所有信号的效果
    for (int i = 1; i <= 31; i++)
    {
        sigaddset(&bset, i);
    }

    // 将进程的当前信号屏蔽字设置为屏蔽所有信号的集合,并保存原始信号屏蔽字
    sigprocmask(SIG_SETMASK, &bset, &oset);

    // 通过信号处理函数捕获信号
    signal(SIGINT, handler);

    // 循环,从 0 到 31 依次给自己发送信号
    for (int signo = 0; signo <= 31; signo++)
    {
        // 获取当前未决状态的信号集合
        sigset_t pending;
        int n = sigpending(&pending);
        if (n < 0)
            continue;

        // 打印未决状态的信号集合
        PrintPending(pending);

        // 给自己发送信号,注意排除 SIGKILL 和 SIGSTOP 信号(9 和 19)
        if (signo != 9 && signo != 19)
        {
            raise(signo); // 或者使用 kill(getpid(), signo);
            usleep(100000); // 等待 100 毫秒
        }
        // 等待1秒
        sleep(1);
    }
    return 0;
}

最终输出:
信号阻塞与捕捉_第2张图片

  1. SIGKILL (9): 这是一个无法被捕获、阻塞或忽略的信号。它的主要作用是立即**终止目标进程,且无法被该进程阻止或捕获。因为 SIGKILL 是一个强制终止信号,所以在正常情况下应该避免将其发送给进程,以防止可能导致数据损坏或其他不良后果。

  2. SIGSTOP (19): 这是一个使目标进程暂停执行的信号。与 SIGKILL 不同,SIGSTOP 可以被捕获,但无法被阻塞或忽略。它通常用于暂停进程以进行调试或其他目的。

  3. SIGCONT (18):主要用于继续恢复被暂停的进程,通常是由于收到SIGSTOPSIGTSTP信号。这是为了实现进程的挂起和恢复功能。如果SIGCONT可以被阻塞,那么进程将无法在被暂停后继续执行。

3. 信号的捕捉

信号阻塞与捕捉_第3张图片

  1. 当信号的处理动作是用户自定义函数时,内核选择执行用户自定义的信号处理函数。
  2. 信号处理函数与原始的主控制流程是独立的控制流程,使用不同的堆栈空间。
  3. 在中断处理后,内核在返回用户态之前执行用户自定义的信号处理函数,而不是恢复原始主控制流程的上下文。
  4. 信号处理函数执行完毕后,通过特殊的系统调用 sigreturn 再次进入内核态。
  5. 如果没有新的信号需要处理,再次返回用户态时会继续原来的主控制流程。

4. sigaction函数

sigaction函数用于设置信号的处理动作,其原型如下:

#include 
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

signo:指定要处理的信号编号。
act:指定新的信号处理行为。
oldact:用于存储原来的信号处理行为,可为nullptr。

sigaction数据结构
信号阻塞与捕捉_第4张图片

sa_handler:一个函数指针,指定了信号的处理函数。
sa_masksa_maskstruct sigaction 结构它是一个信号集,用于指定在执行信号处理函数时需要被阻塞的信号。也就是说,在处理当前信号时,sa_mask 中指定的信号会被阻塞,防止它们干扰当前信号的处理。
sa_flags 字段是一个整数,用于指定一些标志,影响信号处理的行为,这里设置成0即可,这里不做详细介绍。 其他字段也不做介绍

代码示例

#include 
#include 
#include 
#include 

// 打印当前未决信号集合的函数
void PrintPending()
{
    sigset_t set;
    sigpending(&set);
    // 遍历输出信号集合
    for (int signo = 1; signo <= 31; signo++)
        if (sigismember(&set, signo))
            std::cout << "1";
        else
            std::cout << "0";
    std::cout << "\n";
}

// 信号处理函数
void handler(int signo)
{
    std::cout << "捕获到信号,信号编号:" << signo << std::endl;
    // 在信号处理函数中,循环打印当前未决信号集合
    while (true)
    {
        PrintPending();
        sleep(1);
    }
}

int main()
{
    // 创建 sigaction 结构体变量 act 和 oact,用于设置和保存信号处理方式
    struct sigaction act, oact;
    memset(&act, 0, sizeof(act));
    memset(&oact, 0, sizeof(oact));

    // 初始化信号集合 act.sa_mask,清空其中的所有信号位
    sigemptyset(&act.sa_mask);
    // 设置信号集合 act.sa_mask,其中包括信号 1、3、4,同时屏蔽1,3,4号信号
    sigaddset(&act.sa_mask, 1);
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);

    // 设置信号处理函数为 handler
    act.sa_handler = handler; // 也可以设置为 SIG_IGN(忽略信号)或 SIG_DFL(默认处理)

    // 使用 sigaction 函数将信号 2(SIGINT)的处理方式设置为 act,保存原有处理方式到 oact
    sigaction(2, &act, &oact);

    // 循环输出当前进程的PID,可以观察进程运行
    while (true)
    {
        std::cout << "我是进程:" << getpid() << std::endl;
        sleep(1);
    }

    return 0;
}

输出
信号阻塞与捕捉_第5张图片

信号处理函数执行时,当前信号会被加入进程的信号屏蔽字,防止同类信号再次中断处理。函数执行结束后,内核自动还原原来的信号屏蔽字状态。如果需要屏蔽其他信号,可以使用 sa_mask 字段,在处理结束后同样会自动还原。

你可能感兴趣的:(linux,服务器,c语言)