信号

定义:

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

信号的优先级比较高,进程收到信号之后,暂停正在处理的工作,优先处理信号,处理完成之后再继续暂停的工作。

信号的特点及状态

特点: 

简单

携带的信息量少

使用在某个特定的场景中

状态:

产生

未决状态 - 没有被进程处理

递达 - 信号被处理了

产生方式包括

键盘:ctrl+c

命令:kill

系统函数:kill

软条件:定时器

硬件:段错误、除0错误

信号列表:

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

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发给进程组所有的进程
pid<-1发给指定进程组的进程
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则处理该信号

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

自定义信号集相关函数 

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函数执行完,才结束了进程。

常见的信号

SIGINT 终止进程,通常我们的Ctrl+C就发送的这个消息。

SIGQUIT 和SIGINT类似, 但由QUIT字符(通常是Ctrl- / )来控制,进程收到该消息退出时会产生core文件。

SIGKILL 消息编号为9,我们经常用kill -9来杀死进程发送的就是这个消息,程序收到这个消息立即终止,这个消息不能被捕获,封锁或这忽略,所以是杀死进程的终极武器。

SIGSTOP 停止进程的执行,同SIGKILL一样不可以被应用程序所处理,注意它和SIGINT的区别:该进程还未结束, 只是暂停执行。

SIGCHILD 这个同样是高性能服务器需要关注的信号,如果服务器采用fork产生的子进程推出后要调用wait进行资源回收,防止僵尸进程的产生,但是如果程序对子进程退出后的状态不感兴趣的话可以调用signal(SIGCHLD,SIG_IGN); 交给系统init去回收。子进程也不会产生僵尸进程了。

SIGALRM 时钟定时信号, 计算的是实际的时间或时钟时间。alarm函数使用该信号。

两个不能被忽略或捕获的信号:SIGKILL和SIGSTOP

网络编程相关信号

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信号。

可靠信号和不可靠信号(转)

1.可靠信号与不可靠信号有哪些?

       SIGHUP(1号) 至 SIGSYS(31号)之间的信号都是继承自UNIX系统,是不可靠信号,也称为非实时信号;

       SIGRTMIN(33号) 与 SIGRTMAX(64号)之间的信号,它们都是可靠信号,也称为实时信号;

2.什么是可靠信号?

       可靠性是指信号是否会丢失,即该信号是否支持排队;

       如果支持排队就是可靠的,不支持排队就是不可靠的。

3.那么问题来了,在哪里排队?

   在未决信号队列排队。当导致产生信号的事件发生时,内核就产生一个信号。信号产生后,内核通常会在进程表中设置某 种形     式的标志,当内核设置了这个标志,我们就说内核向一个进程递送了一个信号。

   而信号产生(generate)和递送(delivery)之间的时间间隔称为信号未决(pending)。

4.还有,什么情况下会产生排队呢?

    举个栗子,进程可以调用sigpending将某个信号设置为阻塞,即进程产生了一个阻塞的信号,而对该信号的动作是捕捉该信号 (即不忽略信号),则内核将为该进程的此信号保持为未决状态,直到该进程对此信号解除阻塞或将对此信号更改为忽略。

     如果在进程解除对这个信号的阻塞之前,这种信号发生了多次,在这种情况下,将发生未决信号排 队。

5.那个支持排队和不支持排队怎么理解呢?不支持排队就是来了直接扔了吗?

  如果被阻塞的信号来了多个(信号在未决信号队列里排队)

    (1)如果来多少个,就递送多少个。则称为可靠信号,即支持排队。

    (2)如果来了多个,但只被递送一次。则称为不可靠信号,即不支持排队。
 

可重入函数

信号的发生导致程序的指令执行顺序被打乱。

但是在信号处理函数中,无法知道原进程的执行情况。

如果原进程这个在分配内存或者释放内存,或者调用了修改static变量的函数,并在信号处理函数中再次调用该函数,会发生不可预期的结果。

在信号处理函数中可以安全调用的函数称为可重入函数,也叫做异步信号安全的函数。除了保证可重入,这些函数还会阻塞可能导致结果不一致的信号。

如果函数满足下面的一种或者几种条件,则说明是不可重入的函数:

  • 使用static数据结构
  • 调用malloc或free
  • 标准IO库中的函数,因为大部分的标准IO函数都使用了全局数据结构

信号处理函数处理信号时是否会被其他信号中断?

void myfunc(int sig)
{
        sleep(10);
        printf("cathc you signal: %d\n", sig);
}
 
void myfunc1(int sig)
{
        //sleep(10);
        printf("cathc you signal: %d\n", sig);
}
int main(int argc, const char* argv[])
{
        //ctrl+c
        // 注册信号捕捉函数
        signal(SIGINT, myfunc);
        signal(SIGQUIT, myfunc1);
        while(1)
        {   
                printf("hello\n");
                sleep(2);
        }   
        return 0;
}

从实验结果可以看出,一个信号的信号处理函数过程是可以被其他信号中断的,但如果是同一个信号再次来临,则是发生阻塞,并将多个相同信号合并为一个,待处理完前面那个信号之后,再处理后面的这个相同的信号。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值