欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

进程的信号和signal() sigactoin()函数

程序员文章站 2022-03-19 13:12:55
...

使用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) 给自己发送异常中止函数

信号的状态

  1. 信号的状态有 阻塞(block),未决(pending).抵达(deliver)
  2. 未决就是该信号已经发送,但没有被处理
  3. 抵达就是该信号可以被处理了(handler)
    1. 处理方式有: 默认,自定义(hanler),忽略
  4. 如果一个信号被阻塞,那么即使它是未决状态,那么它也不会被抵达

可以用这么一张表来表示

进程的信号和signal() sigactoin()函数
横向看,每个信号都对应一个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的信号进行处理,例如如果信号处理函数是自定义的
那么就会从内核切换回用户态函数执行的地址,执行玩又返回内核态,最后切换至程序原本执行流的地方
相关标签: 信号