使用C语言进行信号处理:从理论到实践的全面指南

news2024/11/25 22:41:06

在这里插入图片描述

1. 引言

在现代操作系统中,信号是一种进程间通信机制,它允许操作系统或其他进程向一个进程发送消息。信号可以用来通知进程发生了一些重要事件,如用户请求终止进程、硬件异常、定时器超时等。掌握信号处理技术对于开发健壮、高效的系统程序至关重要。本文将带你深入了解信号的基础知识,并通过一系列示例演示如何在C语言程序中实现信号处理。

2. 信号概述

信号是由操作系统产生的软件中断,用于通知接收进程发生了某些类型的事件。信号可以分为两大类:

  • 不可忽略的信号:如SIGKILL和SIGSTOP,它们总是会被操作系统强制执行。
  • 可忽略的信号:如SIGINT和SIGTERM,接收进程可以选择忽略或者自定义处理。

常见的信号及其用途如下表所示:

信号编号描述
SIGINT2终端中断信号,通常由用户按下Ctrl+C触发。
SIGTERM15终止信号,通常用于请求程序优雅地停止运行。
SIGKILL9强制终止信号,无法被捕捉或忽略。
SIGALRM14定时信号,由alarm()函数设置的时间间隔到期时产生。
SIGHUP1挂断信号,当控制终端挂起或登录会话结束时产生。
SIGPIPE13管道破裂信号,当写入一个已经断开连接的管道时产生。
SIGUSR110用户定义信号1,用于进程间的通讯。
SIGUSR212用户定义信号2,用于进程间的通讯。

在这里插入图片描述

3. 信号处理基础

在C语言中,信号处理主要依赖于signal()函数。该函数允许用户注册一个信号处理函数,当指定的信号到达时,就会调用这个函数。然而,signal()函数存在一些限制,如不能传递额外参数给信号处理函数,且不是线程安全的。因此,在多线程程序中,更推荐使用sigaction()函数来替代。

3.1 使用signal()函数
#include <signal.h>
#include <stdio.h>

void signal_handler(int signum) {
    printf("Received signal %d\n", signum);
    exit(signum);
}

int main() {
    signal(SIGINT, signal_handler); // 注册信号处理函数
    while (1) {
        printf("Hello World!\n");
        sleep(1);
    }
    return 0;
}

上述代码注册了一个SIGINT信号处理函数signal_handler,当用户按下Ctrl+C时,程序将打印一条消息并退出。

3.2 使用sigaction()函数

sigaction()函数提供了更多的灵活性和安全性,可以设置信号掩码、指定信号处理方式(忽略、默认处理或自定义处理函数)等。

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

void sigint_handler(int signum, siginfo_t *info, void *context) {
    printf("Caught signal %d\n", signum);
    exit(signum);
}

int main() {
    struct sigaction sa;
    sa.sa_sigaction = sigint_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO;

    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    while (1) {
        printf("Hello World!\n");
        sleep(1);
    }
    return 0;
}

在这个版本中,我们使用sigaction()函数注册了一个信号处理程序,并且启用了SA_SIGINFO标志,这允许我们的信号处理函数接受额外的参数。

在这里插入图片描述

4. 信号与线程

在多线程程序中,信号的处理需要特别注意。默认情况下,信号是针对整个进程而不是特定线程的。这意味着,如果一个线程捕获到了信号,所有线程都会受到影响。为了避免这种情况,可以使用pthread_sigmask()函数来设置线程的信号掩码,从而控制哪些信号可以被线程捕获。

4.1 设置线程信号掩码
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

void *thread_func(void *arg) {
    int *num = (int *)arg;
    *num += 1;
    printf("Thread: %d\n", *num);
    pthread_exit(NULL);
}

int main() {
    pthread_t thread_id;
    int num = 0;

    if (pthread_create(&thread_id, NULL, thread_func, &num) != 0) {
        perror("Failed to create thread");
        exit(EXIT_FAILURE);
    }

    sigset_t mask;
    sigfillset(&mask); // 设置信号掩码
    sigdelset(&mask, SIGINT); // 允许SIGINT信号

    if (pthread_sigmask(SIG_BLOCK, &mask, NULL) == -1) {
        perror("Failed to set signal mask");
        exit(EXIT_FAILURE);
    }

    while (1) {
        printf("Main thread: %d\n", num);
        sleep(1);
    }

    return 0;
}

在此示例中,我们创建了一个线程,并设置了信号掩码,使得只有SIGINT信号可以被线程捕获。这样即使在主线程中按下Ctrl+C,也不会影响到正在运行的线程。

5. 定时信号:alarm()sigtimedwait()

除了处理外部信号外,我们还可以通过alarm()函数来设置定时信号。当指定的时间过去之后,SIGALRM信号就会被发送给进程。此外,sigtimedwait()函数提供了一种等待信号的方式,并且可以指定一个超时时间。

5.1 使用alarm()函数
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void alarm_handler(int signum) {
    printf("Alarm signal received.\n");
}

int main() {
    signal(SIGALRM, alarm_handler);
    alarm(5); // 设置5秒后发送SIGALRM信号
    while (1) {
        printf("Waiting...\n");
        sleep(1);
    }
    return 0;
}

此程序将在启动后五秒发出报警信号。

5.2 使用sigtimedwait()函数
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

int main() {
    sigset_t pending;
    sigemptyset(&pending);
    sigaddset(&pending, SIGALRM);
    alarm(5); // 设置5秒后发送SIGALRM信号

    struct timespec timeout = {1, 0}; // 超时时间为1秒
    siginfo_t info;

    while (1) {
        printf("Waiting for a signal...\n");
        if (sigtimedwait(&pending, &info, &timeout) != -1) {
            printf("Signal caught: %d\n", info.si_signo);
        }
        sleep(1);
    }
    return 0;
}

在这个例子中,我们使用sigtimedwait()函数来等待信号,如果在一秒钟内没有信号到来,则会继续循环。

在这里插入图片描述

6. 高级主题:信号队列与实时信号
6.1 信号队列

当一个信号被发送给一个进程时,如果该信号正在被处理或被阻止,则信号会被放入进程的信号队列中。每个进程都有一个信号队列,最多可以存储一个每个类型的信号。当信号队列已满时,再来的相同类型的信号将被丢弃。

信号队列的管理通常是由操作系统完成的,但作为程序员,我们需要知道信号队列的存在,并且在设计程序时考虑到这一点。例如,如果程序频繁地忽略或阻止某个信号,可能导致信号丢失,从而引发不可预期的行为。

6.2 实时信号

实时信号是一组特殊的信号,它们具有更高的优先级,并且可以携带额外的数据。使用sigqueue()函数可以发送实时信号,并附带一个用户定义的值。

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

void real_time_signal_handler(int signum, siginfo_t *info, void *context) {
    printf("Real-time signal %d with value %d\n", signum, info->si_value.sival_int);
}

int main() {
    struct sigaction sa;
    sa.sa_sigaction = real_time_signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO;

    if (sigaction(SIGRTMIN, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    // 发送带有整数值的实时信号
    union sigval value;
    value.sival_int = 1234;
    if (sigqueue(0, SIGRTMIN, value) == -1) {
        perror("sigqueue");
        return 1;
    }

    while (1) {
        printf("Waiting for a real-time signal...\n");
        sleep(1);
    }
    return 0;
}

这段代码演示了如何发送一个带有整数值的实时信号,并在信号处理函数中读取这个值。

7. 实战案例:实现一个简单的守护进程

守护进程(Daemon)是一种在后台运行的服务程序,它不与任何终端关联,并且通常会在系统启动时自动运行。下面我们将展示如何使用信号处理技术来创建一个简单的守护进程。

7.1 创建守护进程

创建守护进程的一般步骤如下:

  1. 第一次fork:创建一个子进程,然后让父进程退出。这是为了防止后续操作受到shell的影响。
  2. 成为会话领导者:通过调用setsid()函数,使进程脱离原来的会话和终端。
  3. 第二次fork:再次创建一个子进程,并让父进程退出。这是因为setsid()只能在一个没有控制终端的进程中调用,否则会失败。
  4. 更改工作目录:将当前工作目录改为根目录,防止进程删除其当前目录而导致进程无法正常工作。
  5. 关闭文件描述符:关闭标准输入、输出和错误文件描述符,防止守护进程占用不必要的资源。
  6. 设置信号处理程序:忽略某些信号,使守护进程更加稳定。
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void daemonize() {
    pid_t pid = fork();
    if (pid < 0) {
        perror("Fork failed");
        exit(EXIT_FAILURE);
    } else if (pid > 0) {
        exit(EXIT_SUCCESS); // 父进程退出
    }

    // 成为会话领导者
    if (setsid() < 0) {
        perror("Setsid failed");
        exit(EXIT_FAILURE);
    }

    // 第二次fork
    pid = fork();
    if (pid < 0) {
        perror("Fork failed");
        exit(EXIT_FAILURE);
    } else if (pid > 0) {
        exit(EXIT_SUCCESS); // 父进程退出
    }

    // 更改工作目录
    if (chdir("/") < 0) {
        perror("Chdir failed");
        exit(EXIT_FAILURE);
    }

    // 关闭文件描述符
    umask(0);
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    // 设置信号处理程序
    signal(SIGHUP, SIG_IGN);
    signal(SIGINT, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);
    signal(SIGTERM, exit);
}

int main() {
    daemonize();

    while (1) {
        printf("Daemon running...\n");
        sleep(10);
    }

    return 0;
}

这个简单的守护进程忽略了大多数信号,只对SIGTERM信号作出响应,即当接收到终止信号时退出。

在这里插入图片描述

8. 高级实战案例:守护进程与信号处理

让我们进一步扩展之前的守护进程示例,使其成为一个更加实用的服务程序。我们将添加日志记录功能,并且允许守护进程通过信号进行重启、停止等操作。

8.1 日志记录

在守护进程中添加日志记录功能可以帮助我们跟踪程序的状态和错误。我们可以将日志输出到一个文件中,这样即使程序崩溃,我们也能够查看到它最后的状态。

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define LOG_FILE "/var/log/mydaemon.log"

void log(const char *message) {
    int fd = open(LOG_FILE, O_WRONLY | O_APPEND | O_CREAT, 0644);
    if (fd == -1) {
        perror("Open log file failed");
        return;
    }
    fprintf(fd, "%s\n", message);
    close(fd);
}

void daemonize() {
    pid_t pid = fork();
    if (pid < 0) {
        perror("Fork failed");
        exit(EXIT_FAILURE);
    } else if (pid > 0) {
        exit(EXIT_SUCCESS); // 父进程退出
    }

    if (setsid() < 0) {
        perror("Setsid failed");
        exit(EXIT_FAILURE);
    }

    pid = fork();
    if (pid < 0) {
        perror("Fork failed");
        exit(EXIT_FAILURE);
    } else if (pid > 0) {
        exit(EXIT_SUCCESS); // 父进程退出
    }

    if (chdir("/") < 0) {
        perror("Chdir failed");
        exit(EXIT_FAILURE);
    }

    umask(0);
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    signal(SIGHUP, SIG_IGN);
    signal(SIGINT, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);
    signal(SIGTERM, exit);
}

void handle_signals(int signum) {
    switch (signum) {
        case SIGHUP:
            log("SIGHUP received, reloading configuration...");
            break;
        case SIGTERM:
            log("SIGTERM received, shutting down...");
            exit(EXIT_SUCCESS);
        default:
            log("Unknown signal received.");
            break;
    }
}

int main() {
    daemonize();

    // 设置信号处理函数
    signal(SIGHUP, handle_signals);
    signal(SIGTERM, handle_signals);

    while (1) {
        log("Daemon running...");
        sleep(10);
    }

    return 0;
}

在这个版本中,我们添加了一个log()函数,用于将消息输出到日志文件中。同时,我们修改了信号处理函数handle_signals(),使其能够根据不同类型的信号采取不同的行动。

9. 总结与展望

通过本文,你不仅了解了信号的基本概念和用途,还学会了如何在C语言程序中使用信号处理技术。从简单的信号处理到复杂的守护进程创建,每一步都充满了挑战与乐趣。希望这些知识能够帮助你在未来的开发过程中更好地利用信号机制来提升程序的健壮性和可用性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2237762.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据入门-什么是HBase

目录 一、概念 二、架构 1.Client 2.Zookeeper 3.HMaster 4.HRegionServer 三、特性 1.数据存储庞大 2.支持随机读写 3.轻松融入生态 4.数据强一致性 5.性能足够高效 四、适用场景 五、其他事宜 1.权益备注 2.支持博主 大数据入门系列文章 这里简单介绍的HBas…

【笔记】自动驾驶预测与决策规划_Part6_不确定性感知的决策过程

文章目录 0. 前言1. 部分观测的马尔可夫决策过程1.1 POMDP的思想以及与MDP的联系1.1.1 MDP的过程回顾1.1.2 POMDP定义1.1.3 与MDP的联系及区别POMDP 视角MDP 视角决策次数对最优解的影响 1.2 POMDP的3种常规解法1.2.1 连续状态的“Belief MDP”方法1. 信念状态的定义2. Belief …

Vue2中使用firefox的pdfjs进行文件文件流预览

文章目录 1.使用场景2. 使用方式1. npm 包下载,[点击查看](https://www.npmjs.com/package/pdfjs-dist)2. 官网下载1. 放到public文件夹下面2. 官网下载地址[点我,进入官网](https://github.com/mozilla/pdf.js/tags?afterv3.3.122) 3. 代码演示4. 图片预览5. 如果遇到跨域或者…

MFC图形函数学习06——画椭圆弧线函数

绘制椭圆弧线函数是MFC基本绘图函数&#xff0c;这个函数需要的参数比较多&#xff0c;共四对坐标点。前两对坐标点确定椭圆的位置与大小&#xff0c;后两对坐标确定椭圆弧线的起点与终点。 一、绘制椭圆弧线函数 原型&#xff1a;BOOL Arc(int x1,int y1,int x2,int y2…

新版 idea 编写 idea 插件时,启动出现 ClassNotFound

IntelliJ IDEA 2024.1.6 (Ultimate Edition) Build #IU-241.19072.14, built on August 8, 2024 Licensed to Sophia Tout Subscription is active until June 29, 2025. For educational use only. Runtime version: 17.0.111-b1207.30 amd64 Kotlin: 241.19072.14-IJ 新版本…

信息安全工程师(83)Windows操作系统安全分析与防护

一、Windows操作系统安全分析 系统漏洞&#xff1a; Windows操作系统由于其复杂性和广泛使用&#xff0c;可能存在一些已知或未知的漏洞。这些漏洞可能会被黑客利用&#xff0c;进行恶意攻击。微软会定期发布系统更新和补丁&#xff0c;以修复这些漏洞&#xff0c;提高系统的安…

【嵌入式开发——ARM】1ARM架构

嵌入式领域&#xff0c;使用ARM架构的芯片公司可不占少数吧&#xff0c;intel的x86架构主要占据PC、服务器市场&#xff0c;ARM架构主要占据移动市场。x86架构和ARM架构不同的主要原因&#xff0c;是背后使用的计算机指令集不同。计算机有自己的语言系统&#xff08;汇编&#…

31-自定义地图:分层地图

利用自定义地图中的级别&#xff0c;可以让多个人同时在一张地图上工作。它还允许您在仿真过程中使用Python API在地图上加载和卸载层&#xff0c;就像分层的CARLA地图一样(layered CARLA maps)。 本指南将解释如何添加新级别&#xff0c;如何向级别添加资产&#xff0c;以…

操作系统-磁盘

文章目录 磁盘的结构一、磁盘的物理结构二、磁盘的逻辑结构 磁盘的调度算法磁盘时间算法先来先服务&#xff08;FCFS - First-Come, First-Served&#xff09;最短寻道时间优先&#xff08;SSTF - Shortest Seek Time First&#xff09;扫描算法&#xff08;SCAN&#xff0c;也…

【Linux】【信号操作】汇总整理

信号&#xff08;Signals&#xff09;是操作系统中用于通知进程发生特定事件的一种机制。信号可以由软件或硬件触发&#xff0c;并且可以被进程捕获和处理。以下是信号的相关概念、常见信号列表、信号处理以及相关API的汇总整理。 信号概述 信号是操作系统向进程发出的通知&am…

必备的计算机软件专业资料汇总,包括:计算机专业实习报告,计算机毕业设计成品(含源码和论文1900套)

大学期间必备的计算机软件专业资料汇总&#xff0c;包括&#xff1a;计算机专业实习报告&#xff08;58篇&#xff09;、计算机毕业设计成品&#xff08;含源码和论文&#xff0c;1900多套&#xff0c;包括C语言/PHP/VB/java/JSP/Andorid/Python/微信小程序等&#xff09;、HTM…

业务模块部署

一、部署前端 1.1 window部署 下载业务模块前端包。 &#xff08;此包为耐威迪公司发布&#xff0c;请联系耐威迪客服或售后获得&#xff09; 包名为&#xff1a;业务-xxxx-business &#xff08;注&#xff1a;xxxx为发布版本号&#xff09; 此文件部署位置为&#xff1a;……

使用kalibr_calibration标定相机(realsense)和imu(h7min)

vslam-evaluation/VINS/Installation documentation/4.IMU和相机联合标定kalibr_calibration.md at master DroidAITech/vslam-evaluation GitHub 目录 1.kalibr安装 1.1安装依赖项 1.2创建工作空间 1.3下载kalibr并编译 1.4设置环境变量 2.准备标定板 3.配置驱动和打…

Java | Leetcode Java题解之第551题学生出勤记录I

题目&#xff1a; 题解&#xff1a; class Solution {public boolean checkRecord(String s) {int absents 0, lates 0;int n s.length();for (int i 0; i < n; i) {char c s.charAt(i);if (c A) {absents;if (absents > 2) {return false;}}if (c L) {lates;if …

MATLAB课程:AI工具辅助编程——MATLAB+LLMs

给出一些可能有用的方法辅助大家写代码。 方法一&#xff1a;MATLAB软件LLM (不太懂配置的同学们为了省事可以主要用这个方法) 方法一特别针对本门MATLAB教学课程&#xff0c;给出一种辅助ai工具的操作指南。MATLAB中可以安装MatGPT插件&#xff0c;该插件通过调用ChatGPT的API…

腾讯混元3D-1.0:文本到三维和图像到三维生成的统一框架

虽然三维生成模型极大地改进了艺术家的工作流程&#xff0c;但现有的三维生成扩散模型存在生成速度慢、泛化能力差的问题。 为了解决这个问题&#xff0c;我们提出了一种名为 "Hunyuan3D-1.0 "的两阶段方法&#xff0c;包括精简版和标准版&#xff0c;均支持文本和图…

现代Web开发:Vue 3 组件化开发实战

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 现代Web开发&#xff1a;Vue 3 组件化开发实战 现代Web开发&#xff1a;Vue 3 组件化开发实战 现代Web开发&#xff1a;Vue 3 组…

VBA08-if语句

一、单行 If 语句 If x > 10 Then MsgBox "x is greater than 10"二、多行 If...Then...End If 语句 If x > 10 ThenMsgBox "x is greater than 10"y x 5 End If 三、If...Then...Else 语句 If condition Then 当条件为真时执行的代码块stateme…

深度学习中的感受野:从基础概念到多层次特征提取

在深度学习&#xff0c;特别是计算机视觉任务中&#xff0c;感受野&#xff08;Receptive Field&#xff09;是一个至关重要的概念。它指的是在神经网络中某一层的神经元在输入图像上“看到”的区域大小。感受野的大小影响了网络能捕捉的特征层级&#xff0c;从而决定了它的特征…

Jekins篇(搭建/安装/配置)

目录 一、环境准备 1. Jenkins安装和持续集成环境配置 2. 服务器列表 3. 安装环境 Jekins 环境 4. JDK 环境 5. Maven环境 6. Git环境 方法一&#xff1a;yum安装 二、JenKins 安装 1. JenKins 访问 2. jenkins 初始化配置 三、Jenkins 配置 1. 镜像配置 四、Mave…