当前位置: 首页 > article >正文

Linux系统编程之基本信号处理

概述

        在前一篇文章中我们已经提到过,信号本质上是一个整数编号,每个编号对应一种特定类型的事件。当某个条件满足时,操作系统会生成相应的信号,并将其传递给目标进程。接收到信号后,进程可以选择忽略它、执行默认动作、或调用自定义的处理函数来响应。

signal函数

        捕获信号最简单的方式是使用signal函数,它允许我们指定当特定信号到达时要执行的动作。其函数原型如下。

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

        各个参数和返回值的含义如下。

        signum:待捕获信号的编号,如SIGINT、SIGTERM等。

        handler:指向信号处理函数sighandler_t的指针。我们可以提供一个函数名作为参数,该函数将在接收到指定信号时被调用。这个函数应该接受一个整型参数(即信号编号),并且没有返回值。handler参数还可以是以下两个特殊值之一。

        (1)SIG_DFL:恢复默认行为。对于大多数信号,默认行为是终止进程。对于某些信号(比如:SIGCHLD),则是忽略它们。

        (2)SIG_IGN:忽略此信号。如果信号被忽略,那么即使它再次发生也不会有任何效果,除非后续又改变了信号处理方式。

        返回值:成功时返回一个指向先前信号处理程序的指针,可用来恢复之前的处理方式。失败时返回SIG_ERR,可通过errno获取具体的错误代码。

        在下面的示例代码中,我们定义了一个名为HandleSigint的函数来处理SIGINT信号。当用户按下Ctrl+C时,操作系统会向进程发送SIGINT信号。然后,我们的处理函数会被调用,打印一条消息并退出程序。

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

static void HandleSigint(int signum)
{
    printf("Caught SIGINT(%d)\n", signum);
    exit(0);
}

int main()
{
    // 设置SIGINT信号的处理器
    if (signal(SIGINT, HandleSigint) == SIG_ERR)
    {
        printf("Set handler failed\n");
        return 1;
    }

    printf("Press Ctrl+C to send SIGINT\n");

    while (1)
    {
        // 等待信号
        pause();
    }

    return 0;
}

        可以看到,signal函数的使用相对比较简单,但它也有一些局限性和潜在的问题。

        1、不可靠性。在某些系统上,使用signal函数设置的信号处理器可能会被重置为默认行为。这意味着,如果同一个信号再次到来,在处理完第一次之后,后续的信号将按照默认行为处理。

        2、线程安全性。由于signal函数可能会覆盖已有的信号处理程序,并且它的实现可能不是线程安全的,在多线程环境中应谨慎使用。

        因此,如果需要对信号进行更精细的控制,或确保更可靠的行为时,建议使用下面的sigaction函数。

sigaction函数

        sigaction函数提供了比signal函数更加灵活和可靠的接口,允许我们为特定的信号指定更复杂的处理行为。通过sigaction,我们可以控制信号处理期间的行为,比如:是否重启被中断的系统调用、哪些信号应该在处理期间被阻塞等。

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

        各个参数和返回值的含义如下。

        signum:待捕获信号的编号,如SIGINT、SIGTERM等。

        act:指向sigaction结构体的指针,描述了新的信号处理动作。如果这个参数为NULL,则只查询当前的信号处理信息而不修改它。

        oldact:也是指向sigaction结构体的指针,用来存储当前信号处理的信息。如果不需要保存旧的处理信息,可以将此参数设为NULL。

        sigaction结构体的原型如下。

struct sigaction
{
    union
    {
        // 基本信号处理函数
        void (*sa_handler)(int);
        // 扩展信号处理函数,需要SA_SIGINFO标志
        void (*sa_sigaction)(int, siginfo_t *, void *);
    } __sigaction_handler;
    // 在信号处理期间要阻塞的其他信号集合
    sigset_t sa_mask;
    // 控制信号处理行为的标志位
    int sa_flags;
    // 已废弃,不应使用
    void (*sa_restorer)(void);
};

        返回值:成功时返回0,失败时返回-1,并设置errno以指示具体的错误原因。

        在下面的示例代码中,我们不仅设置了SIGINT信号处理器,还利用sa_mask字段暂时屏蔽了SIGQUIT信号,防止两个信号同时到来造成混乱。另外,通过设置SA_RESTART标志,确保被信号中断的系统调用(比如:read或write函数)能够自动重启,而不是返回EINTR错误码。

        对于SIGUSR1信号,我们选择了更详细的处理方式:启用了SA_SIGINFO标志,并使用sa_sigaction成员来获取更多关于信号的信息(比如:发送信号的进程ID)。

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

// 基本信号处理函数
static void HandleSigint(int signum)
{
    printf("Caught SIGINT(%d)\n", signum);
    exit(0);
}

// 扩展信号处理函数,需要SA_SIGINFO标志
static void HandleSigusr1(int signum, siginfo_t *info, void *context)
{
    printf("Caught SIGUSR1(%d) from PID %d\n", signum, info->si_pid);
}

int main(void)
{
    // 设置SIGINT信号的处理器
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = &HandleSigint;
    sigemptyset(&sa.sa_mask);
    // 在处理SIGINT时,屏蔽SIGQUIT
    sigaddset(&sa.sa_mask, SIGQUIT);
    // 自动重启被中断的系统调用
    sa.sa_flags = SA_RESTART;
    sigaction(SIGINT, &sa, NULL);

    // 设置SIGUSR1信号的处理器
    sa.sa_sigaction = &HandleSigusr1;
    // 启用SA_SIGINFO标志
    sa.sa_flags |= SA_SIGINFO;
    sigaction(SIGUSR1, &sa, NULL);
   
    printf("Press Ctrl+C to send SIGINT or use kill -s SIGUSR1 <pid>\n");
    while (1)
    {
        pause();
    }

    return 0;
}

http://www.kler.cn/a/547778.html

相关文章:

  • linux--关于makefile
  • 如何使用UniApp实现页面跳转和数据传递?
  • iOS实现生物识别
  • 【k8s应用管理】kubernetes 安全机制
  • 【prompt实战】旅行攻略顾问
  • PHP 基础介绍
  • 青少年编程与数学 02-009 Django 5 Web 编程 14课题、命名空间
  • 2024-arXiv-LlamaFactory: 统一高效微调100多种语言模型
  • 英码科技基于昇腾算力实现DeepSeek离线部署
  • 第十五届蓝桥杯嵌入式省赛真题(满分)
  • 【办公类-91-01】20250214“每周安排表”批量填写——数字“年月日”、文字“休息、节假日”
  • SYN-TFO伪造攻击.c
  • 算法面试题
  • 17.企业级知识图谱中的知识库全景解析(基本概念、 5W2H视角知识库、存储格式分类与技术对比、实践路径与架构设计、案例)
  • 《On Java中文版基础卷+进阶卷》
  • typecho快速发布文章
  • Acwing-基础算法课笔记之基础算法(双指针)
  • 系统不是基于UEFI的win11,硬盘格式MBR,我如何更改为GPT模式添加UEFI启动?
  • 借助天工AI 生成产品彩页体验 (5G 远距CPE产品彩页)
  • Centos搭建python环境