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

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

在这里插入图片描述

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);}// 第二次forkpid = 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语言程序中使用信号处理技术。从简单的信号处理到复杂的守护进程创建,每一步都充满了挑战与乐趣。希望这些知识能够帮助你在未来的开发过程中更好地利用信号机制来提升程序的健壮性和可用性。

相关文章:

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

1. 引言 在现代操作系统中&#xff0c;信号是一种进程间通信机制&#xff0c;它允许操作系统或其他进程向一个进程发送消息。信号可以用来通知进程发生了一些重要事件&#xff0c;如用户请求终止进程、硬件异常、定时器超时等。掌握信号处理技术对于开发健壮、高效的系统程序至…...

什么是工单管理系统?全面认识指南

在现代企业中&#xff0c;客户服务和支持是业务成功的关键因素之一。为了有效地管理客户请求和问题&#xff0c;许多公司采用了工单管理系统。本文将深入探讨工单管理系统的定义、功能、优势。 一、工单管理系统的定义 工单管理系统是一种软件工具&#xff0c;旨在帮助企业管…...

集群化消息服务解决方案

目录 集群化消息服务解决方案项目概述架构图使用说明服务端通过API接口推送消息给客户端调用方式 请求参数返回参数 客户端推送消息连接websocket或发送消息 接收消息项目地址作者信息 集群化消息服务解决方案 项目概述 集群化消息服务解决方案是一种用于处理大量消息的高可用…...

python数据结构操作与可视化的应用

Python具有丰富的数据结构操作和可视化库&#xff0c;可以进行各种数据结构的创建、编辑和分析&#xff0c;并将结果可视化。以下是几个常见的Python数据结构操作和可视化的应用示例&#xff1a; 1. 列表&#xff08;List&#xff09;操作和可视化&#xff1a; - 创建列表&a…...

【基于轻量型架构的WEB开发】课程 作业4 AOP

一. 单选题&#xff08;共7题&#xff0c;38.5分&#xff09; 1 (单选题)下列选项中&#xff0c;用于通知/增强处理的是&#xff08; &#xff09;。 A. Joinpoint B. Pointcut C. Aspect D. Advice 正确答案&#xff1a;D 答案解析&#xff1a;在面向切面编程&#xff…...

跨境独立站新手,如何用DuoPlus云手机破局海外社媒引流?

独立站作为电商领域的一个重要组成部分&#xff0c;其发展在最近几年里确实令人瞩目&#xff0c;对于想要进入跨境赛道的新手卖家来说&#xff0c;手上握着有优势的货源&#xff0c;建立小型的DTC独立站确实会比入驻第三方平台具有更大的灵活性。本文将给跨境卖家们总结独立站和…...

【Android、IOS、Flutter、鸿蒙、ReactNative 】标题栏

Android 标题栏 参考 Android Studio版本 配置gradle镜像 阿里云 Android使用 android:theme 显示标题栏 添加依赖 dependencies {implementation("androidx.appcompat:appcompat:1.6.1")implementation("com.google.android.material:material:1.9.0")…...

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

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

QT Unknown module(s) in QT 以及maintenance tool的更详细用法(qt6.6.0)

不小心接了同事的委托&#xff0c;帮改一个qt的工程代码。然后出事了&#xff0c;那个proj是qt5.9版本的吧&#xff0c;搞到6.6版本的环境中各种问题。至少有3个是这样的&#xff1a; :-1: error: Unknown module(s) in QT: multimedia 直接百度&#xff0c;好像很简单&#x…...

如何在vscode中安装git详细新手教程

一、安装git后点击vscode中的设置 今天教大家如何在VScode中编写代码后提交到git仓库&#xff0c;如果我们不想切换到git的命令行窗口&#xff0c;可以在VScode中配置git&#xff0c;然后就可以很方便快捷的把代码提交到仓库中。 二、在输入框中输入 git.path &#xff0c;再点…...

JVM垃圾回收详解二(重点)

死亡对象判断方法 堆中几乎放着所有的对象实例&#xff0c;对堆垃圾回收前的第一步就是要判断哪些对象已经死亡&#xff08;即不能再被任何途径使用的对象&#xff09;。 引用计数法 给对象中添加一个引用计数器&#xff1a; 每当有一个地方引用它&#xff0c;计数器就加 1…...

VLAN 高级技术实验

目录 一、实验背景 二、实验任务 三、实验步骤 四、实验总结 一、实验背景 假如你是公司的网络管理员&#xff0c;为了节省内网的IP地址空间&#xff0c;你决定在内网部署VLAN聚合&#xff0c;同时为了限制不同业务之间的访问&#xff0c;决定同时部署MUX VLAN。 二、实验…...

windowsC#-创建和引发异常

异常用于指示在运行程序时发生了错误。 此时将创建一个描述错误的异常对象&#xff0c;然后使用 throw 语句或表达式引发。 然后&#xff0c;运行时搜索最兼容的异常处理程序。 当存在下列一种或多种情况时&#xff0c;程序员应引发异常&#xff1a; 1. 方法无法完成其定义的…...

python爬虫案例——请求的网页源码被加密,解密方法全过程(19)

文章目录 1、任务目标2、网页分析3、代码编写1、任务目标 目标网站:https://jzsc.mohurd.gov.cn/data/company,该网站的网页源码被加密了,用于本文测验 要求:解密该网站的网页源码,请求网站并返回解密后的明文数据,网页内容如下: 2、网页分析 进入网站,打开开发者模式,…...

详解广告联盟

某种程度上&#xff0c;动荡的程度甚于以往。产业链中快速挤进了众多不曾有过的角色&#xff0c;产业逻辑被完全颠覆。巨大的变化在几年间迅速产生&#xff0c;源头是快速发展的互联网和科技。 这个行业走到了十字路口&#xff0c;身处其中的大多数人感到乐观&#xff0c;但同…...

Getting accurate time estimates from your tea(从您的团队获得准确的时间估计)

Hi again. 嗨了。 Ready to get back into it? 准备好重新开始了吗&#xff1f; Let’s go. Time estimation, 我们走吧。时间估计, effort estimation, 努力估计, and capacity planning are all helpful techniques for creating your project schedule. 容量规划都是创建项…...

攻防世界35-easyupload-CTFWeb

攻防世界35-easyupload-CTFWeb 通过各种上传发现&#xff0c;过滤了php后缀和内容中有php的文件 有这几种方式上传一句话木马 <script language"php">eval($_POST[1]);</script> <?php eval($_POST[cmd]);?> <? eval($_POST[cmd]);?>…...

在Mysql中,如何定位慢查询

参考回答&#xff1a;之前我们有个项目做压测的时候有的接口非常的慢&#xff0c;接口的响应时间超过了2秒以上&#xff0c;因为在MySOL中也提供了慢日志查询的功能&#xff0c;可以在MySOL的系统配置文件中开启这个慢日志的功能&#xff0c;并且也可以设置SOL执行超过多少时间…...

CSS教程(三)- CSS 三大特性

1. 层叠性 介绍 多组CSS样式共同作用于一个元素&#xff0c;就会出现 覆盖&#xff08;层叠&#xff09; 另一个冲突的样式。 层叠原则 样式冲突&#xff1a;遵循就近原则&#xff08;哪个样式离结构近&#xff0c;就执行哪个样式&#xff09; 样式不冲突&#xff0c;就不会重…...

如何保证Redis与MySQL双写一致性

什么是双写一致性问题&#xff1f; 双写一致性主要指在一个数据同时存在于缓存&#xff08;如Redis&#xff09;和持久化存储&#xff08;如MySQL&#xff09;的情况下&#xff0c;任何一方的数据更新都必须确保另一方数据的同步更新&#xff0c;以保持双方数据的一致状态。这一…...

RestClient

什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端&#xff0c;它允许HTTP与Elasticsearch 集群通信&#xff0c;而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级&#xff…...

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

tomcat入门

1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效&#xff0c;稳定&#xff0c;易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...

从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障

关键领域软件测试的"安全密码"&#xff1a;Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力&#xff0c;从金融交易到交通管控&#xff0c;这些关乎国计民生的关键领域…...

前端高频面试题2:浏览器/计算机网络

本专栏相关链接 前端高频面试题1&#xff1a;HTML/CSS 前端高频面试题2&#xff1a;浏览器/计算机网络 前端高频面试题3&#xff1a;JavaScript 1.什么是强缓存、协商缓存&#xff1f; 强缓存&#xff1a; 当浏览器请求资源时&#xff0c;首先检查本地缓存是否命中。如果命…...

大模型真的像人一样“思考”和“理解”吗?​

Yann LeCun 新研究的核心探讨&#xff1a;大语言模型&#xff08;LLM&#xff09;的“理解”和“思考”方式与人类认知的根本差异。 核心问题&#xff1a;大模型真的像人一样“思考”和“理解”吗&#xff1f; 人类的思考方式&#xff1a; 你的大脑是个超级整理师。面对海量信…...

新版NANO下载烧录过程

一、序言 搭建 Jetson 系列产品烧录系统的环境需要在电脑主机上安装 Ubuntu 系统。此处使用 18.04 LTS。 二、环境搭建 1、安装库 $ sudo apt-get install qemu-user-static$ sudo apt-get install python 搭建环境的过程需要这个应用库来将某些 NVIDIA 软件组件安装到 Je…...

VSCode 没有添加Windows右键菜单

关键字&#xff1a;VSCode&#xff1b;Windows右键菜单&#xff1b;注册表。 文章目录 前言一、工程环境二、配置流程1.右键文件打开2.右键文件夹打开3.右键空白处打开文件夹 三、测试总结 前言 安装 VSCode 时没有注意&#xff0c;实际使用的时候发现 VSCode 在 Windows 菜单栏…...