当前位置: 首页 > 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;以保持双方数据的一致状态。这一…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析

今天聊的内容&#xff0c;我认为是AI开发里面非常重要的内容。它在AI开发里无处不在&#xff0c;当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗"&#xff0c;或者让翻译模型 "将这段合同翻译成商务日语" 时&#xff0c;输入的这句话就是 Prompt。…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器

一.自适应梯度算法Adagrad概述 Adagrad&#xff08;Adaptive Gradient Algorithm&#xff09;是一种自适应学习率的优化算法&#xff0c;由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率&#xff0c;适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)

目录 1.TCP的连接管理机制&#xff08;1&#xff09;三次握手①握手过程②对握手过程的理解 &#xff08;2&#xff09;四次挥手&#xff08;3&#xff09;握手和挥手的触发&#xff08;4&#xff09;状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

376. Wiggle Subsequence

376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

Python爬虫(一):爬虫伪装

一、网站防爬机制概述 在当今互联网环境中&#xff0c;具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类&#xff1a; 身份验证机制&#xff1a;直接将未经授权的爬虫阻挡在外反爬技术体系&#xff1a;通过各种技术手段增加爬虫获取数据的难度…...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...