admin管理员组文章数量:1613777
windows和linux下c/c++程序的退出事件/信号处理
- 引言
- atexit
- SetConsoleCtrlHandler
- Linux 信号处理
- 有哪些信号
- 简单的signal捕捉
- 信号集
- 信号屏蔽字(block) && 信号未决字(pending)
- 屏蔽信号
- sigaction函数与sigaction结构体
- sigaction结构体
- sigaction函数
- sigsuspend 信号暂停
引言
Linux的信号
和Windows中的事件
很像,朋友问我有没有办法让一个控制台程序退出前执行
某些东西,所以就可以写在一起了,本文也正好写一篇关于Linux中信号
的东西。
atexit
这玩意在windows
和linux
中都可以使用,属于std(标准库)
中的,并且其可以注册多个
。
#include <stdlib.h>
void ExitRoutine1(void) {
printf("准备退出\n");
Sleep(2000);
}
void ExitRoutine2(void) {
printf("退出了\n");
}
int main(){
//注册顺序和执行顺序相反,没看源码猜测是栈式存储,后入先出
//int atexit (void(*function)(void));
atexit(ExitRoutine2);
atexit(ExitRoutine1);
while(1){}//死循环
return 0;
}
需要注意的是,这玩意如果是控制台程序
,对于ctrl+c
之类的不起作用
,只有exit()
或关闭窗口
时才会起作用。
SetConsoleCtrlHandler
这玩意对于Windows控制台
的ctrl+c
等等事件
进行监听。
BOOL WINAPI HandlerRoutine(DWORD dwCtrlType) {
switch (dwCtrlType)
{
case CTRL_C_EVENT: //CTRL + C
MessageBox(NULL, L"CTRL + C", L"提示", MB_OK);
break;
case CTRL_BREAK_EVENT: //CTRL + BREAK
break;
case CTRL_CLOSE_EVENT: //关闭
MessageBox(NULL, L"关闭事件", L"提示", MB_OK);
break;
case CTRL_LOGOFF_EVENT: //用户退出
MessageBox(NULL, L"用户退出,系统注销", L"提示", MB_OK);
break;
case CTRL_SHUTDOWN_EVENT: //系统被关闭时.
MessageBox(NULL, L"系统关闭", L"提示", MB_OK);
break;
}
return 0;
}
int main(){
//第二个参数FALSE为卸载钩子
SetConsoleCtrlHandler(HandlerRoutine, TRUE);
while(1){} //死循环
return 0;
}
Linux 信号处理
Linux
中关于信号
的应用实际上我们用的很频繁,比如kill
命令,很多后端程序都有kill -s SIGHUP
或kill -HUP
来实现平滑重启
,实际上kill
命令和Windows
中的taskkill
有本质区别
,Linux
中的kill
实际上只是对程序发送信号
。
有哪些信号
通过kill -l
可以查看所有信号
。
# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
简单的signal捕捉
signal(int, void (*)(int));
第一个参数为要处理的信号
第二个参数为函数指针
或地址
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void handler(int sig)
{
printf("got signal %d\n", sig);
}
int main()
{
signal(SIGINT, handler);
while(1)
{
printf("lalalalala...\n");
Sleep(1000);
}
}
上面的程序会帮我们处理SIGINT
,也就是interrupt
,在Linux
中体现为CTRL+C
。
信号集
信号集
说的就是信号的集合
。在C
中为sigset_t
结构体,大小是64bits
,注意哦是64位
,不是64字节,每个信号
占1位
。
信号屏蔽字(block) && 信号未决字(pending)
信号屏蔽字
,是由一个信号集合
组成,64bits
,每个信号
占1位
,如果bit
为1
,表示该信号
对于当前进程
来说是被屏蔽的
。信号未决字
,是由一个信号集合
组成,64bits
,每个信号
占1位
,如果bit
为1
,表示该信号
对于当前进程
来说是还没有被处理过的
。
屏蔽信号
需要注意的是有些信号无法屏蔽
,这是系统内核本身就有的规则,比如SIGKILL
,如果这玩意都能被屏蔽,写的后门岂不是无法结束。
栗子:
sigset_t blockSet;
sigset_t oldSet;
//设置SIGINT信号
sigemptyset(&blockSet); //全部置0
sigaddset(&blockSet, SIGINT); //将中断信号加入信号集中
//对blockSet中的所有信号进行屏蔽,并且将之前的信号集保存在oldSet
//sigprocmask不光可以SIG_BLOCK,还可以SIG_SETMASK
sigprocmask(SIG_BLOCK, &blockSet, &oldSet);
如果使用上面的代码屏蔽掉SIGINT
,那么当对进程发送interrupt
信号时,就会被屏蔽
,此时会这个信号会变成未决状态
。
使用sigpending
,可以将当前未决状态
统计到sigset_t(信号集)
结构中,然后我们就可以sigismember(用来测试信号是否已加入至信号集里)
判断并且打印这个信号集
了。
sigset_t pset;
while(1){
sigpending(&pset);
for (int i = 1; i <= 64; ++i){
if(sigismember(pset, i))
printf("1 ");
else
printf("0 ");
if(i % 8 == 0){
printf("\n"); //每8个换一行输出
}
}
}
sigaction函数与sigaction结构体
没错,你没看错,函数和结构体同名。
sigaction结构体
struct sigaction {
void (*sa_handler)(int); /*信号处理函数*/
sigset_t sa_mask; /*信号屏蔽集,可以通过函数sigemptyset/sigaddset等来清空和增加需要屏蔽的信号。*/
int sa_flags; /*SA_RESETHAND SA_RESTART SA_NODEFER*/
/*
另外一个信号处理函数
它有三个参数,可以获得关于信号的更详细的信息。
当 sa_flags 成员的值 包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理函数。
在某些系统中,成员 sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。
sa_mask 成员用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。
*/
void (*sa_sigaction)(int, siginfo_t *, void *);
};
sigaction函数
struct sigaction newact,oldact;
//这个地方也可以是函数
//SIG_IGN就会ignore(忽略)这个信号
newact.sa_handler = SIG_IGN;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
//signal(SIGINT, SIG_IGN); 相同
sigaction(SIGINT,&newact,&oldact);//原来的备份到oldact里面
//恢复
Sleep(5000);
sigaction(SIGINT, &oact, NULL);
sigsuspend 信号暂停
这玩意是最难理解的一个函数,且整个过程为原子操作,其说明写在了注释里。
这玩意主要是用来代替pause
,用在程序在SIG_SETMASK
和pause
之间如果来信号不会错过信号。
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void handler(int sig) //信号处理程序
{
if(sig == SIGINT)
printf("SIGINT sig");
else if(sig == SIGQUIT)
printf("SIGQUIT sig");
else
printf("SIGUSR1 sig");
}
int main()
{
sigset_t newset,oldset,waitset; //三个信号集
struct sigaction act;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
//可以捕捉以下三个信号:SIGINT/SIGQUIT/SIGUSR1
sigaction(SIGINT, &act, 0);
sigaction(SIGQUIT, &act, 0);
sigaction(SIGUSR1, &act, 0);
sigemptyset(&newset);
sigaddset(&newset, SIGINT); //SIGINT信号加入到newset信号集中
sigemptyset(&waitset);
sigaddset(&waitset, SIGUSR1); //SIGUSR1信号加入wait
sigprocmask(SIG_BLOCK, &newset, &oldset); //将SIGINT阻塞,保存当前信号集到oldset中
//我们本意是要屏蔽newset中的SIGINT
//但是sigsuspend执行,程序在此处会挂起
//并且将进程的信号屏蔽字设置为waitset(之前是newset)
//导致我们变成收到waitset中的SIGUSR1信号会阻塞掉,所以程序继续挂起,如果我们waitset为空,将不会阻塞任何信号,来任何信号都将会唤醒程序。
//此时过来waitset以外的其他信号,则会唤醒程序,并且将该进程的信号屏蔽字替换回sigsuspend之前的newset
if(sigsuspend(&waitset) != -1)
printf("error");
printf("sigsuspend end");
sigprocmask(SIG_SETMASK, &oldset, NULL);
return 0;
}
版权声明:本文标题:windows和linux下cc++程序的退出事件信号处理 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/xitong/1728659413a1168526.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论