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

信号

程序员文章站 2022-07-12 10:29:51
...

定义:

在计算机科学中,信号是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。

信号的特点及状态

特点: 

简单

携带的信息量少

使用在某个特定的场景中

状态:

产生

未决状态 - 没有被处理

递达 - 信号被处理了

信号列表:

信号

进程收到信号的三种处理方式

1.默认处理:

忽略信号:内核将信号丢弃,信号没有对进程产生任何影响

终止进程:进程异常终止

产生核心转储文件,同时终止文件

停止进程:暂停进程的执行

恢复之前暂停的进程继续执行

2.忽略处理:

信号来了不做任何处理
注意:不能忽略SIGKILL和SIGSTOP,保护操作系统的方式,必须能够有强制性的手段杀死进程


3.捕获并处理:

信号来了捕获信号,并执行程序员自己写的程序

注意:不能捕获SIGKILL和SIGSTOP,原因同上

信号相关函数

kill

函数原型:

#include <sgnal.h>

int kill(pid_t pid, int sig);

参数:

pid:可能选择有以下四种

pid的取值 所代表的意义
pid>0 将此信号发送给进程ID为pid的进程
pid==0 将此信号发送给进程组ID和该进程相同的进程
pid<0 将此信号发送给进程组内进程ID为pid的进程
pd==-1 将此信号发送给系统所有的进程

sig:表示要发送的信号的编号,假如其值为0则没有任何信号送出,但是系统会执行错误检查,通常会利用sig值为0来检验某个进程是否仍在执行。

返回值:

成功返回0,失败返回-1

代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>


int main(int argc, const char* argv[])
{
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork error");
        exit(1);
    }

    if(pid > 0)
    {
        while(1)
        {
            printf("parent process pid = %d\n", getpid());
            sleep(1);
        }
    }
    else if(pid == 0)
    {
        sleep(2);
        // 杀死父亲
        kill(getppid(), SIGKILL);
    }

    return 0;
}

信号

raise

作用:

自己给自己发信号

函数原型:

int raise(int sig);

参数:

sig:表示要发送的信号的编号,假如其值为0则没有任何信号送出,但是系统会执行错误检查,通常会利用sig值为0来检验某个进程是否仍在执行。

返回值:

成功返回0,失败返回-1

代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>


int main(int argc, const char* argv[])
{
    pid_t pid = fork();

    if(pid > 0)
    {
        //父进程回收子进程资源
        int s;
        wait(&s);
        if(WIFSIGNALED(s))
        {
            printf("term by signal: %d\n", WTERMSIG(s));
        }
    }
    else if(pid == 0)
    {
        //自己给自己发信号
        raise(SIGQUIT);
    }

    return 0;
}

信号

abort

函数原型:

void abort(void);

说明:

给自己发送异常终止信号

没有参数没有返回值,永远不会调用失败

信号

代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>


int main(int argc, const char* argv[])
{
    pid_t pid = fork();

    if(pid > 0)
    {
        //父进程回收子进程资源
        int s;
        wait(&s);
        if(WIFSIGNALED(s))
        {
            printf("term by signal: %d\n", WTERMSIG(s));
        }
    }
    else if(pid == 0)
    {
        //自己给自己发信号
        while(1)
        {
                abort();
        }
    }

    return 0;
}

 

信号

alarm

函数原型:

unsigned int alarm(unsigned int seconds);

参数:

定时秒数

函数说明:

设置定时器(每个进程只有一个定时器)

使用的自然定时法,不受进程状态的影响,也就是说时间一直在走

当时间到达之后, 函数发出一个信号:SIGALRM

信号

返回值:

如果在seconds秒内再次调用了alarm函数设置了新的闹钟,则后面定时器的设置将覆盖前面的设置,即之前设置的秒数被新的闹钟时间取代;当参数seconds为0时,之前设置的定时器闹钟将被取消,并将剩下的时间返回

信号

代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>


int main(int argc, const char* argv[])
{
        int ret = alarm(5);
        printf("ret = %d\n", ret);

        sleep(2);

        //重新设置定时器
        ret = alarm(2);
        printf("ret = %d\n", ret);

        while(1)
        {
                printf("hello\n");
                sleep(1);
        }

        return 0;
}

信号
setitimer

函数原型:

int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));

struct itimerval {
  struct timeval it_interval;
  struct timeval it_value;
};
struct timeval {
  long tv_sec;
  long tv_usec;
};

说明:

定时器,实现周期性定时

参数:

which为定时器类型,setitimer支持3种类型的定时器:

ITIMER_REAL: 以系统真实的时间来计算,它送出SIGALRM信号。
ITIMER_VIRTUAL: -以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。
ITIMER_PROF: 以该进程在用户态下和内核态下所费的时间来计算,它送出SIGPROF信号。

it_interval指定间隔时间,it_value指定初始定时时间。如果只指定it_value,就是实现一次定时;如果同时指定 it_interval,则超时后,系统会重新初始化it_value为it_interval,实现重复定时;两者都清零,则会清除定时器。

tv_sec提供秒级精度,tv_usec提供微秒级精度,以值大的为先,注意1s = 1000000us。

ovalue用来保存先前的值,常设为NULL。

返回值:

和alarm函数类似

代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/time.h>
#include <signal.h>

void myfunc(int sig)
{
        printf("起床\n");
}

int main(int argc, const char* argv[])
{
        //设置定时器
        struct itimerval new_value;

        //周期性定时
        new_value.it_interval.tv_sec = 3;
        new_value.it_interval.tv_usec = 0;

        //第一次触发时间
        new_value.it_value.tv_sec = 2;
        new_value.it_value.tv_usec = 0;

        signal(SIGALRM, myfunc);
        //倒计时2s
        setitimer(ITIMER_REAL, &new_value, NULL);
        while(1)
        {
        }
        return 0;
}

信号

信号集

概念

未决信号集:

没有被当前进程处理的信号

阻塞信号集:

将某个信号放到阻塞信号集,这个信号就不会被进程处理
阻塞解除之后,信号被处理

未决信号集与阻塞信号集的关系

信号产生,信号处于未决状态,进程收到信号之后,信号被放入未决信号集

放入未决信号集中的信号等待处理,在处理之前需要做一些事情:判断阻塞信号集中该信号对应的标志位是否为1,如果为1,不处理,如果是0则处理该信号

当阻塞信号集中该信号对应的标志位为1时,该信号被处理

信号

自定义信号集相关函数 

int sigemptyset(sigset_t *set);  将set集合置空

int sigfillset(sigset_t *set); 将所有信号加入set集合

int sigaddset(sigset_t *set,int signo);     将signo信号加入到set集合

int sigdelset(sigset_t *set,int signo);   从set集合中移除signo信号

int sigismember(const sigset_t *set,int signo); 判断信号是否存在

sigpending函数

作用:

读取当前进程的未决信号集

函数原型:

int sigpending(sigset_t *set)

参数:

内核将未决信号集写入set

返回值:

函数调用成功返回0,否则返回-1;

sigprocmask函数

作用:

将自定义信号集设置给阻塞信号集

函数原型:

int sigprocmask(int how, t_t *set,sigset_t *oldset);

参数:

how:用于指定信号修改的方式,可能选择有三种

SIG_BLOCK//将set所指向的信号集中包含的信号加到当前的信号掩码中。即信号掩码和set信号集进行或操作。

SIG_UNBLOCK//将set所指向的信号集中包含的信号从当前的信号掩码中删除。即信号掩码和set进行与操作。

SIG_SETMASK //将set的值设定为新的进程信号掩码。即set对信号掩码进行了赋值操作。

set:为指向信号集的指针,在此专指新设的信号集,如果仅想读取现在的屏蔽值,可将其置为NULL。

oldset:也是指向信号集的指针,在此存放原来的信号集。可用来检测信号掩码中存在什么信号。

返回值:

成功执行时,返回0。失败返回-1,errno被设为EINVAL。

代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/time.h>
#include <signal.h>

int main(int argc,const char* argv[])
{
        //手动屏蔽信号
        //自定义信号集集合
        sigset_t myset;
        //清空集合
        sigemptyset(&myset);
        //添加要阻塞的信号
        sigaddset(&myset,SIGINT); //ctrl+c
        sigaddset(&myset,SIGQUIT); //ctrl+反斜杠
        sigaddset(&myset,SIGKILL);//反例,不能设置为阻塞的信号

        //自定义集合数据设置给内核的阻塞信号集
        sigprocmask(SIG_BLOCK,&myset,NULL);

        //每隔1s读一次内存的未决信号集,并把未决信号集的值输出到屏幕
        while(1)
        {
                sigset_t pendset;
                //注意:读取的是未决信号集,不是阻塞信号集
                sigpending(&pendset);
                //1-31
                for(int i=1;i<32;++i)
                {
                        //对每一个信号一次判断
                        if(sigismember(&pendset,i))
                        {
                                printf("1");
                        }
                        else
                        {
                                printf("0");
                        }
                }
                printf("\n");
                sleep(1);
        }
        return 0;
}
         

信号

说明,开始时未决信号集中的各信号都被置位0,即默认不阻塞。但按下ctrl+c时,进程会从阻塞信号集中判断该信号对应的标志位是否为1,因为设置了,所以未决信号集中的该信号置为1。

信号捕捉函数 

siganl

函数原型:

ypedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

参数:

第一个参数是要捕捉的信号
第二个参数表示我们要对信号进行的处理方式

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>

void myfunc(int sig)
{
        printf("cathc you signal: %d\n", sig);
}

int main(int argc, const char* argv[])
{
        //ctrl+c
        // 注册信号捕捉函数
        signal(SIGINT, myfunc);

        while(1)
        {
                printf("hello\n");
                sleep(2);
        }
        return 0;
}

 

信号

sigaction

函数原型:

int sigaction(int signum,
        const struct sigaction *act,
                struct sigaction *oldact
           );

参数:

signum:要操作的信号。

act:要设置的对信号的新处理方式,指向sigaction结构的指针。

oldact:原来对信号的处理方式。一般为NULL

struct sigaction {
void     (*sa_handler)(int);//和下面的sa_sigaction二者选一
void     (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t   sa_mask;//在信号处理函数执行过程中,临时屏蔽指定的信号
int    sa_flags;//只需记住,若是sa_handler,则sa_flags必为0                  
};

返回值:

0 表示成功,-1 表示有错误发生。

代码


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>

void myfunc(int sig)
{
    printf("hello signal: %d\n", sig);
    sleep(5);
    printf("wake up .....\n");
}

int main(int argc, const char* argv[])
{
    // 注册信号捕捉函数
    struct sigaction act;
    act.sa_flags = 0;
    act.sa_handler = myfunc;
    // 设置临时屏蔽的信号
    sigemptyset(&act.sa_mask);  // 0
    // ctrl + 反斜杠
    sigaddset(&act.sa_mask, SIGQUIT);

    sigaction(SIGINT, &act, NULL);

    while(1);

    return 0;
}

信号

说明:sigemptyset(&act.sa_mask)是设置临时屏蔽的信号,比如按了ctrl+c,此时进入信号处理函数myfunc,然后接着按下ctrl+\,本应该立马结束程序,但是ctrl+\被设置为临时屏蔽的信号,所以等到myfunc函数执行完,才结束了进程。

网络编程相关信号

SIGHUP

SIGHUP 信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联. 系统对SIGHUP信号的默认处理是终止收到该信号的进程。所以若程序中没有捕捉该信号,当收到该信号时,进程就会退出。   

SIGHUP会在以下3种情况下被发送给相应的进程:   

1.终端连接中断时,SIGHUP 会被发送到控制进程,后者会在将这个信号转发给会话中所有的进程组之后自行了断;   

2.session首进程退出时,该信号被发送到该session中的前台进程组中的每一个进程;   

3.若父进程退出导致进程组成为孤儿进程组,且该进程组中有进程处于停止状态(收到SIGSTOP或SIGTSTP信号),该信号会被发送到该进程组中的每一个进程。

SIGPIPE

默认情况下,往一个读端关闭的管道或socket连接中写数据将引发SIGPIPE信号。我们需要在代码中捕获并处理该信号,或者至少忽略它,因为程序接受到SIGPIPE信号的默认行为是结束进程,而我们绝对不希望因为错误的写操作而导致程序退出。引发SIGPIPE信号的写操作将设置errno为EPIPE。

SIGURG

在Linux环境下,内核通知应用程序带外数据到达主要有两种方法:一种是I/O复用技术,select等系统调用在接收到带外数据时将返回,并向应用程序报告socket上的异常事件;另一种方法就是使用SIGURG信号。