c/c++之信号处理<signal.h>
该库提供了一组用于处理信号的函数和宏。信号是由操作系统或程序本身生成的一种异步事件,用于通知某些事件的发生,例如非法操作、用户中断等。
信号
信号是进程之间通信的重要方式。信号是一种异步通知机制,由操作系统或其他进程发送给当前进程,用于通知当前进程发生了某些事件,该进程程序捕获到这个信号后触发相应的处理逻辑。信号捕捉是指进程在接收到信号后采取的行动,而信号处理则是指对接收到的信号进行适当的处理逻辑。
信号捕捉
当信号的处理动作是用户自定义的函数,并且在信号到达时调用该函数,这被称为信号捕捉。信号处理函数运行在用户空间。
信号处理函数:用于捕获和处理各种信号
①signal
设置信号处理程序
函数原型:
void (*signal(int sig, void (*func)(int)))(int);
参数:
- int sig:信号编号
- void (*func)(int):一个函数指针,指向接受一个int参数并返回void的函数
注意:
void (*)(int)
这是一个函数指针,指向的函数接受一个int参数并返回void,即返回的是当前绑定的信号处理函数,对于下面的例子中的prevSignalHandler就可以认为是函数mySignalHandler。
示例:
void mySignalHandler(int sig) { ....... }
void newSignalHandler(int sig) { ..... }
void (*prevSignalHandler)(int) = signal(SIGINT, mySignalHandler);
signal(SIGINT, newSignalHandler);
signal(SIGINT, prevSignalHandler);
说明:当用户按下 Ctrl+C 发送 SIGINT 信号时,程序会调用相应的信号处理函数,
例如在程序中,mySignalHandler和 newSignalHandler 就是处理 SIGINT 信号的处理函数。
②raise
会向当前进程发送指定的信号,并触发信号的处理机制。如果进程对信号sig有自定义的信号处理函数,则调用该处理函数。如果信号未被捕获,操作系统会执行默认行为
函数原型:
int raise(int sig);
参数:
sig:要发送的信号编号,例如SIGINT、SIGTERM等,信号编号通常定义在<signal.h><csignal>中
返回值:
成功时返回0,失败时返回非零值,通常不会失败,因为信号是发送给自己的进程。
示例:
触发信号处理函数
#include <signal.h>
#include <stdio.h>void signalHandler(int sig) {if (sig == SIGINT) {printf("Received SIGINT signal\n");}
}int main() {signal(SIGINT, signalHandler); // 注册 SIGINT 信号处理函数raise(SIGINT); // raise 发送信号 SIGINT,触发信号处理函数 signalHandlerreturn 0;
}
信号宏:用于表示不同类型的信号
示例:
以SIGTERM为例
当执行下面代码时,在另一个终端中执行kill -SIGTERM 1234(执行该代码的进程号)时会向执行该代码的进程发送一个终止信号,执行该代码的进程检测到该信号后会执行signalHandler函数
#include <signal.h>
#include <stdio.h>
#include <unistd.h>void signalHandler(int sig) { if (sig == SIGTERM) {printf("Received SIGTERM signal\n");}
}int main() {signal(SIGTERM, signalHandler); // 注册 SIGTERM 信号的处理函数printf("Program is running. PID: %d\n", getpid());while (true) { // 程序会持续运行,等待信号sleep(1); // 使程序在每个循环中暂停,避免占用过多 CPU}return 0;
}
或者直接通过raise向自身进程发送终止信号,此时也会调用signalHandler函数
#include <signal.h>
#include <stdio.h>
#include <unistd.h>void signalHandler(int sig) { // signalHandler 应该有一个 int 参数if (sig == SIGTERM) {printf("Received SIGTERM signal\n");}
}int main() {signal(SIGTERM, signalHandler); // 注册 SIGTERM 信号的处理函数printf("Program is running. Sending SIGTERM to itself...\n");sleep(2); // 等待一段时间,模拟程序运行raise(SIGTERM); // 向自己发送 SIGTERM 信号while (true); // 程序会持续运行,等待信号return 0;
}
信号集操作函数
①sigemptyset和sigfillset
sigemptyset:用于初始化信号集为空
int sigemptyset(sigset_t* set);
sigfillset:用于将系统所支持的所有信号都填充到信号集中
int sigfillset(sigset_t* set);
示例:
#include <iostream>
#include <csignal> // 提供 sigset_t,sigemptyset,sigfillset 等函数
#include <cstring> // 提供 strerrorint main() {// 定义一个信号集对象sigset_t sigset;// 1. 使用 sigemptyset 将信号集初始化为空if (sigemptyset(&sigset) != 0) {std::cerr << "Failed to initialize empty signal set: " << strerror(errno) << std::endl;return 1;}std::cout << "Signal set initialized to empty successfully." << std::endl;// 此时,sigset 中不包含任何信号// 2. 使用 sigfillset 将系统支持的所有信号加入到信号集中if (sigfillset(&sigset) != 0) {std::cerr << "Failed to fill signal set: " << strerror(errno) << std::endl;return 1;}std::cout << "Signal set filled with all signals successfully." << std::endl;// 此时,sigset 中包含了系统支持的所有信号return 0;
}
②sigaddset和sigdelset
sigaddset:用于将指定信号添加到信号集中
int sigaddset(sigset_t* set, int signum);
//将信号SIGINT加入到信号集中
sigaddset(&set, SIGINT);
sigdelset:用于将指定信号从信号集中删除
int sigdelset(sigset_t* set, int signum);
//从信号集中删除SIGINT这个信号
sigdelset(&set, SIGINT);
③sigismember
检查指定信号是否在信号集中
返回值:
=1在
=0不在
int ret = sigismember(&sigset, SIGINT);
if (ret == 1) {std::cout << "SIGINT is in the signal set (unexpected)." << std::endl;
} else if (ret == 0) {std::cout << "SIGINT is NOT in the signal set (expected)." << std::endl;
} else {std::cerr << "Error checking signal set: " << strerror(errno) << std::endl;return 1;
}
④sigprocmask
用于设置或查询当前的信号屏蔽
int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);
根据how的选择将set向信号屏蔽字中设置,并将查询到的老的信号屏蔽字存储到oldset中
参数:
how:指定如何修改信号屏蔽字
- SIG_BLOCK:将set中的信号添加到当前信号屏蔽字中,阻塞这些信号
- SIG_UNBLOCK:将set中的信号从当前信号屏蔽字中移除,解除阻塞
- SIG_SETMASK用set中的信号集完全替换当前的信号屏蔽字。
注意:信号屏蔽字是指一个进程中用于记录当前被阻塞的信号集和的数据结构,它是由操作系统内核维护的用来表示哪些信号在当前进程中 被阻塞(即不会被立即传递和处理),而是被暂时挂起。
set:指向一个sigset_t类型的信号集,表示要修改或设置的信号集合。如果为NULL表示不修改信号屏蔽字,仅查询当前屏蔽字,查询结果存储在oldset中
oldset:指向一个sigset_t类型的信号集,用于存储调用sigprocmask之前的信号 屏蔽字。如果不需要保存旧的信号屏蔽字,可以传入NULL。
使用 sigprocmask 屏蔽 SIGINT 信号
if (sigprocmask(SIG_BLOCK, &set, &oldset) != 0) {std::cerr << "Failed to block SIGINT: " << strerror(errno) << std::endl;return 1;
}
std::cout << "SIGINT is now blocked. Try pressing Ctrl+C, it won't terminate the program." << std::endl;// 让程序睡眠 10 秒,此时 SIGINT 被屏蔽,按 Ctrl+C 没反应
sleep(10);恢复之前的信号屏蔽字(解除 SIGINT 屏蔽)
if (sigprocmask(SIG_SETMASK, &oldset, nullptr) != 0) {std::cerr << "Failed to restore old signal mask: " << strerror(errno) << std::endl;return 1;
}
std::cout << "SIGINT is now unblocked. Try pressing Ctrl+C again." << std::endl;// 再睡眠 10 秒,这时按 Ctrl+C,会触发信号处理器
sleep(10);
⑤sigpending
获取当前进程的未决信号集。未决信号集包含了所有当前被阻塞且已经产生的信号。这些信号尚未被交付给进程,因为他们被当前的信号屏蔽字阻塞。(当信号被屏蔽时,发送信号会导致信号进入未决信号集)。
int sigpending(sigset_t* pending);
pending:函数会将未决信号集存储到这个变量中
int main() {sigset_t set, pending;// 注册 SIGUSR1 的信号处理函数if (signal(SIGUSR1, signalHandle) == SIG_ERR) {std::cerr << "Failed to register signal handler: " << strerror(errno) << std::endl;return 1;}// 初始化信号集为空if (sigemptyset(&set) != 0) {std::cerr << "Failed to initialize signal set: " << strerror(errno) << std::endl;return 1;}// 将 SIGUSR1 加入信号集if (sigaddset(&set, SIGUSR1) != 0) {std::cerr << "Failed to add SIGUSR1 to signal set: " << strerror(errno) << std::endl;return 1;}// 屏蔽 SIGUSR1 信号if (sigprocmask(SIG_BLOCK, &set, nullptr) != 0) {std::cerr << "Failed to block SIGUSR1: " << strerror(errno) << std::endl;return 1;}std::cout << "SIGUSR1 is now blocked. Raising SIGUSR1..." << std::endl;// 向自己发送一个 SIGUSR1 信号if (raise(SIGUSR1) != 0) {std::cerr << "Failed to raise SIGUSR1: " << strerror(errno) << std::endl;return 1;}// 查询当前挂起(pending)的信号if (sigpending(&pending) != 0) {std::cerr << "Failed to get pending signals: " << strerror(errno) << std::endl;return 1;}// 检查 SIGUSR1 是否在挂起的信号集中int ret = sigismember(&pending, SIGUSR1);if (ret == 1) {std::cout << "SIGUSR1 is pending (blocked and waiting to be handled)." << std::endl;} else if (ret == 0) {std::cout << "SIGUSR1 is NOT pending." << std::endl;} else {std::cerr << "Error checking pending signals: " << strerror(errno) << std::endl;return 1;}// 解除 SIGUSR1 的屏蔽,允许它被处理if (sigprocmask(SIG_UNBLOCK, &set, nullptr) != 0) {std::cerr << "Failed to unblock SIGUSR1: " << strerror(errno) << std::endl;return 1;}std::cout << "SIGUSR1 unblocked. Signal handler should now be invoked." << std::endl;// 给操作系统一点时间处理解除后的信号sleep(1);return 0;
}
⑥sigpsuspend
用于临时替换进程的信号屏蔽字并等待某个信号到达。当信号到达并被处理后,sigsuspend会恢复原来的信号屏蔽字。
int sigsuspend(const sigset_t* mask);
mask:指向一个信号集的指针,用于临时设置进程的信号屏蔽字。在调用sigsuspend时,进程的信号屏蔽字会被替换为mask指定的信号集。
注意:sigsuspend不会永久修改信号屏蔽字,函数返回后信号屏蔽字会自动恢复。
void signalHandle(int signum) {std::cout << "Signal handler called with signal " << signum << "!" << std::endl;
}int main() {sigset_t set, newmask, oldmask;// 注册 SIGUSR1 的信号处理函数if (signal(SIGUSR1, signalHandle) == SIG_ERR) {std::cerr << "Failed to register signal handler: " << strerror(errno) << std::endl;return 1;}// 初始化信号集为空if (sigemptyset(&set) != 0) {std::cerr << "Failed to initialize set: " << strerror(errno) << std::endl;return 1;}// 将 SIGUSR1 加入信号集if (sigaddset(&set, SIGUSR1) != 0) {std::cerr << "Failed to add SIGUSR1 to set: " << strerror(errno) << std::endl;return 1;}// 将 SIGUSR1 屏蔽,阻塞它if (sigprocmask(SIG_BLOCK, &set, &oldmask) != 0) {std::cerr << "Failed to block SIGUSR1: " << strerror(errno) << std::endl;return 1;}std::cout << "SIGUSR1 is now blocked. Ready to suspend and wait for signal..." << std::endl;// 初始化新的信号集:newmask,不屏蔽任何信号if (sigemptyset(&newmask) != 0) {std::cerr << "Failed to initialize newmask: " << strerror(errno) << std::endl;return 1;}// 在调用 sigsuspend 前发送 SIGUSR1 信号if (raise(SIGUSR1) != 0) {std::cerr << "Failed to raise SIGUSR1: " << strerror(errno) << std::endl;return 1;}// 使用 sigsuspend 临时用 newmask 替换信号屏蔽字// 当前屏蔽的 SIGUSR1 被临时放开,允许信号处理函数被执行std::cout << "Calling sigsuspend, waiting for signal..." << std::endl;if (sigsuspend(&newmask) == -1 && errno != EINTR) {std::cerr << "sigsuspend error: " << strerror(errno) << std::endl;return 1;}std::cout << "sigsuspend returned after handling the signal." << std::endl;// 恢复之前的信号屏蔽字if (sigprocmask(SIG_SETMASK, &oldmask, nullptr) != 0) {std::cerr << "Failed to restore old signal mask: " << strerror(errno) << std::endl;return 1;}std::cout << "Signal mask restored. Program exiting normally." << std::endl;return 0;
}
高级信号处理函数
sigaction
检查或修改与指定信号相关联的处理动作(可同时两种操作)
函数原型:
int sigaction(int signum, const struct signaction* act, struct signaction* oldact);
参数:
signum:指出要捕获的信号类型
act:指定信号新的处理方式
oldact:如果不为NULL的话,oldact用于存储当前信号先前的处理方式
返回值:
0表示成功,-1表示有错误发生
相关结构体:
①sigaction
struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t*, void*);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);
}
struct sigaction成员:
- sa_handler:指向信号处理函数的指针。如果设置为非空,当信号signum到达时会调用这个函数。该函数接收一个参数,该参数是信号编号。
- sa_sigaction:指向带扩展信息的信号处理函数的指针。如果sa_flags中设置了SA_SIGINFO标志,那么会使用sa_sigaction而不是sa_handler作为信号处理函数,该函数接收三个参数,参数一int为信号编号,参 数二siginfo_t*指向包含信号附加信息的结构体,参数三void*指向用户上下文信息的指针(通常为ucontext_t)。
注意:在某些系统中sa_handler和sa_sigaction被放在联合体中,因此使用时不要同时设置。
- sa_mask:在信号处理程序运行期间要屏蔽的信号集和。在信号处理执行期间,会自动屏蔽signum信号以防止递归。通过sa_mask,可以指定额外的信号,在处理函数执行期间暂时屏蔽这些信号。
- sa_flags:控制信号处理行为的标志,可以是多个标志的按位或组合
-
sa_flags常见标志位:
-
- sa_restorer:用于恢复信号处理的函数。此字段通常未使用,留为空,在现代linux系统中,由内核负责信号处理的恢复操作,因此不需要设置此字段。
- ②siginfo_t
-
用于在信号处理过程中提供关于新号的详细信息。
-
struct siginfo_t成员
-
- 示例一:
-
void my_handler(int sig) { // ........ }void my_sigaction(int sig, siginfo_t* info, void* context) { // ........... }int example1() {struct sigaction act;act.sa_handler = my_handler;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, SIGTERM); // 屏蔽SIGTERMact.sa_flags = SA_RESTART;sigaction(SIGINT, &act, nullptr); // 安装信号处理程序return 0; }
示例二:
-
std::atomic<int> download_progress{0}; std::atomic<bool> stop_download{false};// 信号处理函数SIGUSR1:显示下载速度 void show_progress(int sig, siginfo_t* info, void* context) {std::cout << sig << " download progress " << download_progress.load() << std::endl;std::cout << "Sender PID: " << info->si_pid << std::endl;std::cout << "Sender UID: " << info->si_uid << std::endl; }// 信号处理函数SIGUSR2:强制停止下载 void stop_handler(int sig, siginfo_t* info, void* context) {std::cout << "Download will be stopped" << std::endl;stop_download.store(true); }// 下载的函数 void simulate_download() {std::cout << "Start download" << std::endl;while (download_progress.load() < 100 && !stop_download.load()) {download_progress.fetch_add(5);std::cout << "Current progress: " << download_progress.load() << "%" << std::endl;sleep(1);}if (stop_download.load()) {std::cout << "Stop download" << std::endl;} else {std::cout << "Download successfully" << std::endl;} }int example2() {struct sigaction sa_usr1, sa_usr2;// 初始化SIGUSR1的信号处理std::memset(&sa_usr1, 0, sizeof(sa_usr1));sa_usr1.sa_sigaction = show_progress; // 设置为带扩展信息的处理函数sigemptyset(&sa_usr1.sa_mask); // 不额外屏蔽其他信号sa_usr1.sa_flags = SA_SIGINFO; // 启用扩展信息处理if (sigaction(SIGUSR1, &sa_usr1, nullptr) == -1) {perror("Error happen");return 1;}// 初始化SIGUSR2的信号处理std::memset(&sa_usr2, 0, sizeof(sa_usr2));sa_usr2.sa_sigaction = stop_handler;sigemptyset(&sa_usr2.sa_mask);sa_usr2.sa_flags = SA_SIGINFO;if (sigaction(SIGUSR2, &sa_usr2, nullptr) == -1) {perror("Error happen");return 1;}simulate_download();return 0; }