七、进程间通信-信号

1、信号

(1)概述

  信号是软件中断,进程接收信号后做出相应响应,它提供了一种处理异步事件的方法。每个信号都有名字,这些名字以SIG开头,信号都定义在<signal.h>头文件中,并且都是正整数常量。

 (2)怎么产生信号

  • 硬件

    • 执行非法指令
    • 访问非法内存
    • 驱动程序
  • 软件

    • Ctrl + C:中断信号。

    • Ctrl + | :退出信号。

    • Ctrl + Z:停止信号。

    • kil命令:程序调用kill()函数。

(3)信号的处理方式

  • 忽略:进程当信号从来没有发生过。

  • 捕获:进程会调用相应的处理函数,进行相应的处理。

  • 默认:使用系统默认处理方式处理信号。

(4)常用信号分析

信号名 信号编号 产生原因 默认处理方式
SIGHUP 1 关闭中断 终止
SIGINT 2 ctrl+c 终止
SIGQUIT 3 ctrl+\ 终止+转储
SIGABRT 6 abort() 终止+转储
SIGPE 8 算术错误 终止  
SIGKILL 9 kill -9 pid 终止,不可捕获/忽略
SIGUSR1 10 自定义   忽略  
SIGSEGV 11 段错误 终止+转储  
SIGUSR2 12 自定义 忽略
SIGALRM 14 alarm() 终止
SIGTERM 15 kill pid 终止
SIGCHLD 17 (子)状态变化 忽略
SIGSTOP 19  ctrl+z   暂停,不可捕获/忽略

 

 (5)设置信号处理方式

#include<signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum,sighandler_t handler);
  •  参数
    • signum: 要设置的信号

    • handler:SIG_IGN:忽略;SIG_DFL:默认

    • void (*sighandler_t)(int):自定义

  • 返回值:
    • 成功:返回上一次设置的handler
    • 失败:SIG_ERR.
  • 实例:CTRL+C触发SIGINT信号,处理信号回调函数

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void signal_handler(int sig)
{
    printf("\nthis signa number is %d\n",sig);

    if(sig == SIGINT)
    {
        printf("I have get SIGINT!\n\n");
        printf("The signal has been restored to the default processing mode!\n\n");
        /*恢复信号为默认情况*/
        signal(SIGINT,SIG_DFL);
    }
}

int main(void)
{
    printf("\nthis is an alarm test function\n\n");
    /*设置信号处理的回调函数*/
    signal(SIGINT,signal_handler);
    while(1)
    {
        printf("waiting for the SIGINT signal,please enter\"Ctrl+C\"...\n");
        sleep(1);
    }
    exit(0);
    return 0;
}

执行结果:

(6)kill函数:给其他进程发送特定的信号

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid,int sig);
  • 参数
    • pid:进程id.
    • sig:要发送的信号。
  • 返回值: 
    • 成功:0
    • 失败:-1 

(7)raise函数:把信号发给进程自身

#include <signal>

int raise(int sig);
  • 参数
    • sig:要发送的信号。
  • 返回值: 
    • 成功:0
    • 失败:非0 

 (8)kiill和raise使用实例

 子进程使用rasie给自身进程发送SIGSTOP暂停信号,然后父进程通过kill发送SIGKILL给子进程杀死子进程。

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

int main(void)
{
    pid_t pid;
    int ret;
    pid = fork();
    if(pid<0)
    {
        printf("create process error\n");
        exit(1);
    }
    else if(pid == 0)
    {
        printf("child is waiting for SIGSTOP signal!!\n\n");

        /*子进程停在这里*/
        raise(SIGSTOP);  //给子进程本身发暂停信号,阻塞到这。

        /*子进程没有机会运行到这里*/
        printf("child won't run here forever");
        exit(0);

    }
    else
    {
        /*睡眠3s,让子进程先执行*/
        sleep(3);

        /*发送SIGKILL信号杀死子进程*/
        if((ret=kill(pid,SIGKILL))==0)  //kill给其他进程发信号
        {
            printf("Parent kill %d!\n\n",pid);
        }
        /*等待子进程退出*/
        wait(NULL);

        /*父进程退出运行*/
        printf("parent exit!\n");
        exit(0);

    }

    return 0;
}

2、信号集处理函数

  内核通过读取未决信号集来判断信号是否应被处理。信号屏蔽字mask可以影响未决信号集。而我们可以在应用程序中自定义set来改变mask。已达到屏蔽指定信号的目的。综上:自定义信号集set(也为一个字,64位)通过信号集操作函数来改变信号屏蔽字mask,然后mask进一步来影响未决信号集,从而控制信号是否应该被屏蔽。后面我们只是研究常规信号,即1~31号!

(1)屏蔽信号集

  • 手动
  • 自动

(2)未处理信号集

  信号如果被屏蔽,则记录在未处理信号集中。

  • 非实时信号(1~31):不排队,只留一个。
  • 实时信号(34~64):排队,保留全部。

(3)信号集相关

sigset_t set; // typedef unsigned long sigset_t; 声明一个信号集变量set,信号集类型为sigset_t,为unsigned long(无符号长整型),64位。

int sigemptyset(sigset_t *set);           //将set所指信号集清0 成功:0;失败:-1

int sigfillset(sigset_t *set);           //将set所指信号集置1 成功:0;失败:-1

int sigaddset(sigset_t *set, int signum);     //将某个信号加入信号集(即将其置1) 成功:0;失败:-1

int sigdelset(sigset_t *set, int signum);     //将某个信号清出信号集(即将其置0) 成功:0;失败:-1

int sigismember(const sigset_t *set, int signum); 判断某个信号是否在信号集中(是否为1)返回值:在集合:1;不在:0;出错:-1

sigset_t类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。
 
使用设置好的信号集去修改信号屏蔽集:
 
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
  • how:
    • SIG_BLOCK:屏蔽某个信号(屏蔽集 | set)
    • SIG_UNBLOCK:打开某个信号(屏蔽集 & (~set))
    • SIG_SETMASK:屏蔽集 = set
  • oldset:保存就的屏蔽集的值,NULL表示不保存

 (4)实例:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void signal_handler(int sig)
{
    printf("hello\n");
    sleep(5);
    printf("world\n");
}

int main(void)
{
    /*设置信号处理的回调函数*/
    signal(SIGINT,signal_handler);
    while(1);
    return 0;
}

 执行结果:

 

 由上面的结果,可知我们发送了一个SIGINT信号后,在回调函数里面打印helloworld,我们按Ctrl+C后执行hello,在这期间,多次按ctrl+C发送SIGINT信号,signal并没有每个信号都进行处理,如果他第一个信号没有处理完,就不会再响应其他信号。

由于SIGINT信号编号为2,是一种非实时信号,所以只保留一个信号响应。

我们发送编号为35的信号,此信号为实时信号,系统排队响应。

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void signal_handler(int sig)
{
    printf("hello\n");
    sleep(5);
    printf("world\n");
}

int main(void)
{
    /*设置信号处理的回调函数*/
    signal(35,signal_handler);
    while(1);
    return 0;
}

 

在一个终端执行程序,在另一个终端通过kill发送编号为35的信号给signalsets的进程,然后就打印出了hello world.

由于编号为35的信号是实时信号,排队处理,所以每个信号都被排队响应

 

 如果我们想实时响应非实时信号,可将信号集的这一信号置为1,如SIGINT信号,修改代码如下:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void signal_handler(int sig)
{
    sigset_t set;
    
    sigemptyset(&set);  //信号集清0
    
    sigaddset(&set,SIGINT);   //将SIGINT加入信号集

    sigprocmask(SIG_UNBLOCK,&set,NULL);  //使用新设置的信号集去修改信号屏蔽集
    printf("hello\n");
    sleep(5);
    printf("world\n");
}

int main(void)
{
    /*设置信号处理的回调函数*/
    signal(SIGINT,signal_handler);
    while(1);
    return 0;
}

执行结果如下:每个SIGINT都会被响应

 

 
posted @ 2022-04-27 09:10  轻轻的吻  阅读(180)  评论(0编辑  收藏  举报