windows和linux下c/c++程序的退出事件/信号处理

引言

Linux的信号Windows中的事件很像,朋友问我有没有办法让一个控制台程序退出前执行某些东西,所以就可以写在一起了,本文也正好写一篇关于Linux中信号的东西。

atexit

这玩意在windowslinux中都可以使用,属于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 SIGHUPkill -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位,如果bit1,表示该信号对于当前进程来说是被屏蔽的
  • 信号未决字,是由一个信号集合组成,64bits,每个信号1位,如果bit1,表示该信号对于当前进程来说是还没有被处理过的

屏蔽信号

需要注意的是有些信号无法屏蔽,这是系统内核本身就有的规则,比如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_SETMASKpause之间如果来信号不会错过信号。

#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;

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

没事干写博客玩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值