进程的信号和signal() sigactoin()函数
使用ctrl + c杀死进程
在Linux中假设我们写了一个死循环的程序,然后运行起来.那么这个程序一直就在执行循环体内的内容.
如果让这个程序停下来呢?我们通常都会用 ctrl + c “杀死”这个程序.
其实ctrl+c是一个硬件中断,当cpu接收到这个硬件中断后,会停止执行用户态当前的代码.
cpu从进程的用户态切换至内核态, 这时候驱动程序将 ctrl +c 翻译为一个SIGINT信号
此时操作系统将该信号记录在进程PCB中( 可以理解为向进程发送了此信号)
当进程的代码从内核态将要返回用户态时,会检查该进程的PCB中信号的状态
如果发现此时SIGINT信号待处理,那么就会去处理这个信号,而这个信号作用就是终止进程.
进程就不会返回到用户态而直接被结束掉了.
了解以上过程后,我们可以了解处到信号是给进程下达特定行为的一种工具.
而且不同的信号,进程都知道该信号的处理方式.
这就像路边的交通灯,当红灯亮的时候 ,采取的动作是刹车.
红灯就是一种信号,而该信号处理方式就是 刹车. 信号和处理方式也应该是事先约定好的.
信号的产生方式
1.通过在终端下按键产生,比如之前提到过的 ctrl + c
2.硬件异常,例如除零 (SIGFPE),访问非法内存(SIGSEGV)
3.在终端下使用kill命令 或者在程序中使用kill()函数
a.kill(pid_t pid, int signo) 向指定进程发送某个信号
b.raise(int signo) 给自己发送信号
c.abort(void) 给自己发送异常中止函数
信号的状态
- 信号的状态有 阻塞(block),未决(pending).抵达(deliver)
- 未决就是该信号已经发送,但没有被处理
- 抵达就是该信号可以被处理了(handler)
- 处理方式有: 默认,自定义(hanler),忽略
- 如果一个信号被阻塞,那么即使它是未决状态,那么它也不会被抵达
可以用这么一张表来表示
—
横向看,每个信号都对应一个BLOCK,pending,和handler状态;
siganl() 函数捕捉信号
我们首先使用使用 sigpromask() 阻塞 sigint信号, 当sigint信号pending时,过五秒解除对sigint的阻塞,那么此刻信号就会被抵达
我们又可以使用signal()函数捕捉抵达的信号,并自定义处理该信号所产生的行为.
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
其中参数部分第二个就是上图中最后一个handler表, 这里hanlder是一个函数指针也是回调函数,他的地址就是自定义信号处理函数void (*sighandler_t)(int);
可以看到处理函数可以带一个int参数
还使用到了 使用到了信号集和一系列关于信号集操作的函数
信号集就是一个集合,表示一个集合里所有信号(1到31号普通信号信号)的状态
信号集操作函数可以改变信号集中具体或者某些信号的状态
并将1到31号信号实事的打印在屏幕上 更直观一些
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
sigset_t s,o,p;
//打印信号
void printSIG(sigset_t* p)
{
int i = 0;
for(;i < 32; i++)
{
if( sigismember(p,i) )
{
printf("1 ");
fflush(stdout);
}
else
{
printf("0 ");
fflush(stdout);
}
}
printf("\n");
}
void hander()
{
printf("i catch you ctrl-c !!!,you can't do it forevor\n");
}
int main()
{
int block_cnt = 0;
sigemptyset(&s);
sigemptyset(&p);
sigaddset(&s,SIGINT);
sigprocmask(SIG_SETMASK,&s,&o);
signal(SIGINT,hander);
while(1)
{
if(block_cnt == 5)
{
printf("you are unfrezee\n");
sigprocmask(SIG_SETMASK,&o,NULL);
}
sleep(1);
sigpending(&p);
printSIG(&p);
//接收到信号五秒后解除信号block
if(sigismember(&p,SIGINT))
{
block_cnt++;;
printf("%d\n",block_cnt);
printf("warning!! you are trying unblocking!!\n");
}
}
}
sigactoin()函数
其实对于信号抵达后的处理,推荐你使用sigactoin()函数,而不是signal()函数
在早些的版本中,signal()信号只能对信号进行一次处理,处理完成后该信号的处理方式就变成了原来的处理方式
但如今sinal()函数也是由sigactoin函数实现的
SYNOPSIS
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
其中结构体 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);
};
使用sigactoin捕获SIGINT信号
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handler(int signo)
{
printf("i've got %d sig\n",signo);
}
struct sigaction sig;
struct sigaction old;
int main()
{
sig.sa_handler = handler;
sigaction(SIGINT,&sig,&old);
while(1)
{
sleep(1);
printf("wait ctrl + c \n");
}
return 0;
}
信号在什么时候抵达
如何信号没有被阻塞而且已经pending了,当进程从用户切换回内核态(一个进程在执行过程会多次从用户态转入内核态)
然后从内核态切回用户态前就会对进程task_struct中pending的信号进行处理,例如如果信号处理函数是自定义的
那么就会从内核切换回用户态函数执行的地址,执行玩又返回内核态,最后切换至程序原本执行流的地方
上一篇: 网站导航设计该具备哪些功能
下一篇: 如何给App设计出不恶心用户的付费点?