1. 捕获CTRL+C
有时候我们希望自己的程序被CTRL+C以后,可以先执行一些收尾的工作才结束,比如释放动态内存,关闭网络端口、保存一些状态日志等等,可以用到C++的signal的机制。
例程如下:
#include <iostream>
#include <thread>
#include <signal.h>
#include <conio.h>
#include <Windows.h>
bool exe_stop = false; // 程序结束标志位
std::thread::id sigint_id;
// Ctrl+C捕获后执行的代码
void sigint_handler(int sig)
{
sigint_id = std::this_thread::get_id(); // 记录执行这个函数的线程号
exe_stop = true; // 改写标志位
}
int main(int argc, char **argv)
{
signal(SIGINT, sigint_handler); // 注册信号处理函数
std::cout << "main thread id = " << std::this_thread::get_id() << std::endl;
while (!exe_stop)
{
std::cout << "loop ..." << std::endl;
Sleep(1000);
}
std::cout << "sigint thread id = " << sigint_id << std::endl;
std::cout << "exe stopped!" << std::endl;
// 下面就可以执行清理的操作了...
_getch();
return 0;
}
主要有以下几点
- 引入头文件<signal.h>
- 实现一个信号处理函数void function(int sig)
- 用库函数signal注册
然后当程序运行期间,我们向终端输入CTRL+C的时候,例程sigint_handler就会执行。
2. 信号处理函数的在哪个线程执行
OK!功能已经实现了。那么还有个疑问,究竟这个信号处理函数在哪个线程执行?因为这个问题会牵涉到在信号处理函数的实现里面我们需不需要考虑临界资源保护和死锁。
根据20次左右的测试结果来看,WINDOWS给我们开辟了一个新的线程去执行信号(CTRL+C)处理函数。在截图中,主线程ID是1148,而信号处理函数的执行线程ID是7304。为了判断这个测试结果是否具有偶然性,我查了一下资料(signal | Microsoft Learn),微软是这么说的
由此看来,实验结果和理论吻合。
3. 信号处理函数实现注意事项
再延伸一下,微软文档有对信号处理函数的实现做了一些要求,原文是这样的:
Don't issue low-level or
STDIO.H
I/O routines (for example,printf
orfread
).Don't call heap routines or any routine that uses the heap routines (for example,
malloc
,_strdup
, or_putenv
). For more information, see malloc.Don't use any function that generates a system call (for example,
_getcwd
ortime
).Don't use
longjmp
unless the interrupt is caused by a floating-point exception (that is,sig
isSIGFPE
). In this case, first reinitialize the floating-point package by using a call to_fpreset
.Don't use any overlay routines.
我的个人理解如下:
- 不要用底层或者标准IO函数,如printf和fread
- 不要申请堆空间(malloc和new)
- 不要进行系统调用(就是要在内核态工作的库函数,一般是需要涉及到物理硬件的操作,如文件IO、内存申请、网络收发,屏幕打印)。实际上我觉得这一条涵盖了上面1和2
- 不要用longjmp函数
- 没看懂,求翻译
简而言之,信号处理函数越简单越好
4. 参考文章
windows 平台的信号处理_window 进程有信号处理-CSDN博客
kill及kill -9的用法及如何实现进程的优雅退出_shell kill -9-CSDN博客