Linux学习之旅(19)-----信号

信号:Linux的信号机制是从unix的基础上发展来的,在linux中一共有64个信号(实际只有62个,另外两个被废弃了),其中前32是从unix中继承来的,用来控制进程。另外32个是liunx的信号有被称为实时信号,用来操作硬件。

在linux系统中通过kill -l命令可以查看这写信号:

Linux学习之旅(19)-----信号

操作系统对信号的处理一般有3种方式:

一、默认 二、忽略   三、捕捉

默认方式:操作系统为每个进程都设置了默认的动作(5种),当进程接收到信号后系统就会使用该信号对应的动作。

这五种动作分别是:

(1)Term:终止当前进程。

(2)Core:终止当前进程并且生成Core文件(用于gdb调试)

(3)Ign:   忽略该信号

(4)Stop:暂停当前进程

(5)Cont:继续执行暂停的程序。

Linux学习之旅(19)-----信号

(*最后一行为该信号在什么情况下会产生)

通过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为支持排队。

Linux学习之旅(19)-----信号

这时我们知道如果需要忽略进程某个信号,只需要将该进程对应的阻塞信号集对应信号位置上的状态位置为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;
}

Linux学习之旅(19)-----信号