Linux学习之旅(19)-----信号
信号:Linux的信号机制是从unix的基础上发展来的,在linux中一共有64个信号(实际只有62个,另外两个被废弃了),其中前32是从unix中继承来的,用来控制进程。另外32个是liunx的信号有被称为实时信号,用来操作硬件。
在linux系统中通过kill -l命令可以查看这写信号:
操作系统对信号的处理一般有3种方式:
一、默认 二、忽略 三、捕捉
默认方式:操作系统为每个进程都设置了默认的动作(5种),当进程接收到信号后系统就会使用该信号对应的动作。
这五种动作分别是:
(1)Term:终止当前进程。
(2)Core:终止当前进程并且生成Core文件(用于gdb调试)
(3)Ign: 忽略该信号
(4)Stop:暂停当前进程
(5)Cont:继续执行暂停的程序。
(*最后一行为该信号在什么情况下会产生)
通过man 7 signal 命令可以查看signal的具体信息。
忽略方式:当某个进程接收到信号,会忽略它,即不去处理。一般的信号都可以被设置忽略,但SIGKILL和SIGSTOP是不能被忽略。(如果可以忽略就会产出内核无法杀死的进程,这会非常的危险)。
捕捉方式:即当进程接收到信号后,不会去使用默认的方法,而是使用用户自定义的方式去处理这个信号。
那信号是如何产生的那?
信号编号 | 信号名称 | 产生原因 |
1 | SIGHUP | 当用户退出Shell时,由该Shell启动的所有进程将收到这个信号,默认动作为终止进程 |
2 | SIGINT | 当用户按下Ctrl+C组合键时,用户终端向该终端启动并正在运行的进程发送此信号,默认终止进程 |
3 | SIGQUIT | 当用户按下Ctrl+\组合键时,用户终端向该终端启动并正在运行的进程发送此信号,默认终止进程 |
4 | SIGILL | CPU检测到某进程执行了非法指令。默认终止进程并产生Core文件 |
5 | SIGTRAP | 由断点或者其他trap指令产生 |
6 | SIGABRT | 调用abort函数时产生 |
7 | SIGBUS | 非法访问内存地址,包括内存对齐出错 |
8 | SIGFPE | 在发生致命的运算错误时发出。包括浮点数运算错误,溢出及除数为0等所有算法错误 |
9 | SIGKILL | 无条件终止进程 |
10 | SIGUSE1 | 用户自定义信号,可以在程序中使用 |
11 | SIGSEGV | 无效内存访问 |
12 | SIGUSR2 | 用户自定义信号,可以在程序中使用 |
13 | SIGPIPE | 向没有读端的管道写数据 |
14 | SIGALRM | 定时器超时 |
15 | SIGTERM | 程序结束信号 |
16 | SIGSTKFLT | Linux专用,数学协处理器的栈异常 |
17 | SIGCHID | 子进程结束时,父进程接收的信号 |
18 | SIGCONT | 继续运行暂停进程执行 |
19 | SIGSTOP | 暂停一个进程 |
20 | SIGTSTP | 交互停止进程(Ctrl+z) |
21 | SIGTTIN | 当一个后台进程试图读控制终端时,终端驱动程序产生 |
22 | SIGTTOU | 当一个后台进程试图写控制终端时,终端驱动程序产生 |
23 | SIGURG | 通知进程已经发生了一个紧急情况。在网络连接上接到非规定的波特率的数据时,此信号可选择的产生。 |
24 | SIGXCPU | 当CPU时间限制超时的时候 |
25 | SIGXFSZ | 超过文件的最大长度设置。 |
26 | SIGVTALRM | 虚拟时钟超时时产生该信号,只计算CPU使用时间 |
27 | SIGPROF | 和SIGVTALRM类似,计算CPU和执行系统调用的时间 |
28 | SIGWINCH | 窗口大小发生变化时产生该信号 |
29 | SIGIO | 向进程发出一个异步的I/O事件 |
30 | SIGGPWR | 关机 |
31 | SIGSYS | 无效的系统调用 |
在进程的PCB中存储两个信号集(未决信号集和阻塞信号集),当进程接收到一个信号时,操作系统内核会将该进程未决信号集中的对应的信号位置为1,表示这个信号还没有被真正的执行,然后去查看对应的阻塞信号集,如果阻塞信号集中对应的状态位为0,那么该信号执行,对应未决信号集中的1重新置为0(递达态)。如果为1,那么这个信号被阻塞。并且前32个信号不支持排队,即如果未决信号集中的状态一旦为1,那么之后同样的信号不会被接收。后32为支持排队。
这时我们知道如果需要忽略进程某个信号,只需要将该进程对应的阻塞信号集对应信号位置上的状态位置为1,就可以了,那具体如何操作那?实际上我们时无法直接去修改PCB中的阻塞信号集的,不过linux系统为我们提供了信号集处理的函数。
信号集的处理函数:
sigset_t为信号集(结构体,占128个字节,可以通过sizeof()查看)
int sigemptyset(sigset_t *set); //将信号集的状态为全部置为0
int sigfillset(sigset_t *set); //将信号集的状态为全部置为1
int sigaddset(sigset_t *set); //将信号集中指定的状态置为1
int sigdelset(sigset_t *set); //将信号集中指定的状态置为0
int sigismember(const sigset_t *set,int signo);//查看信号集中对应信号的状态位
int sigprocmask(int how,const sigset_t *set,sigset_t *oset);
/*
函数功能:读取或更改进程的信号阻塞信号集
参数说明:
(1)how
SIG_BLOCK :将set信号集添加到当前信号集,相当于 mask=mask|set
SIG_UNBLOCK:使用set信号集解除阻塞信号集的信号,相当于 mask=mask&(~set)
SIG_SETMASK:将阻塞信号集替换为set信号集。相当于mask=set
(2)set
set信号集,更改阻塞信号集。
(3)oset
oset传出参数,将阻塞信号集通过oset传出
返回值:
若成功返回0,出错返回-1
*/
我们可以通过sigset_t定义一个信号集,然后通过相应的函数对信号集进行设置,然后将它通过sigprocmask()函数注册进系统中。
int sigpending(sigset_t *set);
/*
函数功能:
读取当前进程的未决信号集,通过set参数传出。
返回值:
成功返回0,失败返回-1
*/
程序:设置阻塞SIGINT(ctrl+c)和SIGQUIT (ctrl+\)10秒
#include <signal.h>
#include <sys/types.h>
#include <stdio.h>
void printsignal(const sigset_t *set)
{
int i;
for(i=1;i<32;++i)
{
//检测阻塞信号是否为1
if(__sigismember(set,i)==1)
{
putchar('1');
}
else
{
putchar('0');
}
}
puts("");
}
int main()
{
sigset_t set,oset;
printf("信号集子节数:%d\n",sizeof(set));
//将自定义信号集中的信号全部清零
sigemptyset(&set);
sigaddset(&set,SIGINT);//ctrl+c
sigaddset(&set,SIGQUIT); //ctrl+\
//将信号集注册到系统的信号集中,NULL的意思为不接收传出的参数
sigprocmask(SIG_BLOCK,&set,NULL);
int i=0;
while(1)
{
//读取当前进程的未决信号集,oset为传出参数
sigpending(&oset);
printsignal(&oset);
if(i==10)
{
//解除quit的阻塞
//sigdelset(&s,SIGQUIT);
//解除阻塞
sigprocmask(SIG_UNBLOCK,&set,NULL);
}
sleep(1);
i++;
}
return 0;
}