ITPub博客

首页 > IT职业 > IT职场 > Linux系统编程之信号中断处理(下)

Linux系统编程之信号中断处理(下)

原创 IT职场 作者:千锋教育官方 时间:2019-09-05 17:40:30 0 删除 编辑


信号集与信号阻塞集

信号集

为了方便对多个信号进行处理,一个用户进程常常需要对多个信号做出处理,在 Linux 系统中引入了信号集(信号的集合)。这个信号集有点类似于我们的 QQ 群,一个个的信号相当于 QQ 群里的一个个好友。

信号集是用来表示多个信号的数据类型(sigset_t ),其定义路径为: /usr/include/i386-linux-gnu/bits/sigset.h 。

信号集相关的操作主要有如下几个函数:

 

#include <signal.h>  

int sigemptyset(sigset_t *set);  

int sigfillset(sigset_t *set);  

int sigismember(const sigset_t *set, int signum);  

int sigaddset(sigset_t *set, int signum);  

int sigdelset(sigset_t *set, int signum);

 

通过例子来查看他的使用方法:

 

#include <signal.h>

#include <stdio.h>

 

int main(int argc, char *argv[])

{

sigset_t set; // 定义一个信号集变量

int ret = 0;

 

sigemptyset(&set); // 清空信号集的内容

// 判断 SIGINT 是否在信号集 set 里

// 在返回 1, 不在返回 0

ret = sigismember(&set, SIGINT);

if(ret == 0){

printf("SIGINT is not a member of set \nret = %d\n", ret);

}

sigaddset(&set, SIGINT); // 把 SIGINT 添加到信号集 set

sigaddset(&set, SIGQUIT);// 把 SIGQUIT 添加到信号集 set

// 判断 SIGINT 是否在信号集 set 里

// 在返回 1, 不在返回 0

ret = sigismember(&set, SIGINT);

if(ret == 1){

printf("SIGINT is a member of set \nret = %d\n", ret);

}

sigdelset(&set, SIGQUIT); // 把 SIGQUIT 从信号集 set 移除

// 判断 SIGQUIT 是否在信号集 set 里

// 在返回 1, 不在返回 0

ret = sigismember(&set, SIGQUIT);

if(ret == 0){

printf("SIGQUIT is not a member of set \nret = %d\n", ret);

}

return 0;

}

 

运行结果:

 

信号阻塞集( 屏蔽集、掩码 )

信号阻塞集也称信号屏蔽集、信号掩码。每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)。

所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。

 

我们可以通过 sigprocmask() 修改当前的信号掩码来改变信号的阻塞情况。

 

所需头文件:

#include <signal.h>

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

功能:

检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。

 

参数:

how: 信号阻塞集合的修改方法,有 3 种情况:

SIG_BLOCK :向信号阻塞集合中添加 set 信号集,新的信号掩码是 set 和旧信号掩码的并集。

SIG_UNBLOCK :从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 set 中的信号。

SIG_SETMASK :将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内容清空,然后按照 set 中的信号重新设置信号阻塞集。

set: 要操作的信号集地址

若 set 为 NULL ,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到 oldset 中。

 

oldset: 保存原先信号阻塞集地址

返回值 :

成功:0 ,

失败:-1 ,失败时错误代码只可能是 EINVAL ,表示参数 how 不合法。

注意:不能阻塞 SIGKILL 和 SIGSTOP 等信号,但是当 set 参数包含这些信号时 sigprocmask() 不返回错误,只是忽略它们。另外,阻塞 SIGFPE 这样的信号可能导致不可挽回的结果,因为这些信号是由程序错误产生的,忽略它们只能导致程序无法执行而被终止。

 

示例代码如下:

 

 

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <signal.h>

 

int main(int argc, char *argv[])

{

sigset_t set; // 信号集合

int i = 0;

sigemptyset(&set); // 清空信号集合

sigaddset(&set, SIGINT); // SIGINT 加入 set 集合

while(1)

{

// set 集合加入阻塞集,在没有移除前,SIGINT 会被阻塞

sigprocmask(SIG_BLOCK, &set, NULL);

for(i=0; i<5; i++)

{

printf("SIGINT signal is blocked\n");

sleep(1);

}

// set 集合从阻塞集中移除

// 假如 SIGINT 信号在被阻塞时发生了

// 此刻,SIGINT 信号立马生效,中断当前进程

sigprocmask(SIG_UNBLOCK, &set, NULL);

for(i=0; i<5; i++)

{

printf("SIGINT signal unblocked\n");

sleep(1);

}

}

return 0;

}

 

运行结果:

 

 

可靠信号的操作

从 UNIX 系统继承过来的信号( SIGHUP ~ SIGSYS ,前 32 个)都是不可靠信号,不支持排队(多次发送相同的信号 , 进程可能只能收到一次,可能会丢失)。

SIGRTMIN 至 SIGRTMAX 的信号支持排队(发多少次 , 就可以收到多少次 , 不会丢失),故称为可靠信号。

可靠信号就是实时信号,非可靠信号就是非实时信号。

signal() 函数只能提供简单的信号安装操作,使用 signal() 函数处理信号比较简单,只要把要处理的信号和处理函数列出即可。

signal() 函数主要用于前面 32 种不可靠、非实时信号的处理,并且不支持信号传递信息。

Linux 提供了功能更强大的 sigaction() 函数,此函数可以用来检查和更改信号处理操作,可以支持可靠、实时信号的处理,并且支持信号传递信息。

 

下面我们一起学习其相关函数的使用。

所需头文件:

#include <signal.h>

int sigqueue(pid_t pid, int sig, const union sigval value);

 

功能:给指定进程发送信号。

 

参数:

pid:  进程号。

sig: 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l ("l" 为字母 ) 进行相应查看。

value:  通过信号传递的参数。

union sigval 类型如下:

union sigval  

{  

    int   sival_int;  

    void *sival_ptr;  

};  

返回值:

成功:0

失败:-1

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

 

功能:

检查或修改指定信号的设置(或同时执行这两种操作)。

参数:

signum :要操作的信号。

act :   要设置的对信号的新处理方式(设置)。

oldact :原来对信号的处理方式(设置)。

如果 act 指针非空,则要改变指定信号的处理方式(设置),如果 oldact 指针非空,则系统将此前指定信号的处理方式(设置)存入 oldact 。

返回值:

成功:0

失败:-1

信号设置结构体:

struct sigaction  

{  

    /*旧的信号处理函数指针*/  

    void (*sa_handler)(int signum) ;  

      

    /*新的信号处理函数指针*/  

    void (*sa_sigaction)(int signum, siginfo_t *info, void *context);  

      

    sigset_t sa_mask;/*信号阻塞集*/  

      

    int sa_flags;/*信号处理的方式*/  

};  

 

 

sa_handler 、 sa_sigaction :信号处理函数指针,和 signal() 里的函数指针用法一样,应根据情况给 sa_sigaction 、 sa_handler 两者之一赋值,其取值如下:

SIG_IGN :忽略该信号

SIG_DFL :执行系统默认动作

 

处理函数名:自定义信号处理函数

sa_mask :信号阻塞集

sa_flags :用于指定信号处理的行为,它可以是一下值的 “ 按位或 ” 组合:

SA_RESTART :使被信号打断的系统调用自动重新发起(已经废弃)

SA_NOCLDSTOP :使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。

SA_NOCLDWAIT :使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。

SA_NODEFER :使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。

SA_RESETHAND :信号处理之后重新设置为默认的处理方式。

SA_SIGINFO :使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。

 

 

 

信号处理函数:

void (*sa_sigaction)( int signum, siginfo_t *info, void *context );

参数说明:

signum :信号的编号。

info :记录信号发送进程信息的结构体,进程信息结构体路径: /usr/include/i386-linux-gnu/bits/siginfo.h ,其结构体详情请点此链接。

context :可以赋给指向 ucontext_t 类型的一个对象的指针,以引用在传递信号时被中断的接收进程或线程的上下文

 

下面我们做这么一个例子,一个进程在发送信号,一个进程在接收信号的发送。

发送信号示例代码:

#include <stdio.h>  

#include <signal.h>  

#include <sys/types.h>  

#include <unistd.h>  

  

/******************************************************* 

*功能:     发 SIGINT 信号及信号携带的值给指定的进程 

*参数:        argv[1]:进程号 

            argv[2]:待发送的值(默认为100) 

*返回值:   0 

********************************************************/  

int main(int argc, char *argv[])  

{  

    if(argc >= 2)  

    {  

        pid_t pid,pid_self;  

        union sigval tmp;  

  

        pid = atoi(argv[1]); // 进程号  

        if( argc >= 3 )  

        {  

            tmp.sival_int = atoi(argv[2]);  

        }  

        else  

        {  

            tmp.sival_int = 100;  

        }  

          

        // 给进程 pid,发送 SIGINT 信号,并把 tmp 传递过去  

        sigqueue(pid, SIGINT, tmp);  

          

        pid_self = getpid(); // 进程号  

        printf("pid = %d, pid_self = %d\n", pid, pid_self);  

          

    }  

      

    return 0;  

}  

 

接收信号示例代码如下:

[cpp] view plaincopy

 

#include <signal.h>  

#include <stdio.h>  

  

// 信号处理回电函数  

void signal_handler(int signum, siginfo_t *info, void *ptr)  

{  

    printf("signum = %d\n", signum); // 信号编号  

    printf("info->si_pid = %d\n", info->si_pid); // 对方的进程号  

    printf("info->si_sigval = %d\n", info->si_value.sival_int); // 对方传递过来的信息  

}  

  

int main(int argc, char *argv[])  

{  

    struct sigaction act, oact;  

      

    act.sa_sigaction = signal_handler; //指定信号处理回调函数  

    sigemptyset(&act.sa_mask); // 阻塞集为空  

    act.sa_flags = SA_SIGINFO; // 指定调用 signal_handler  

      

    // 注册信号 SIGINT  

    sigaction(SIGINT, &act, &oact);  

      

    while(1)  

    {  

        printf("pid is %d\n", getpid()); // 进程号  

          

        pause(); // 捕获信号,此函数会阻塞  

    }  

      

    return 0;  

}  

 

两个终端分别编译代码,一个进程接收,一个进程发送,运行结果如下:

 

 

最后:

关注回复“物联网”即可获取物联网全套视频教程

 


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69914734/viewspace-2656155/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
千锋IT职业教育——做真实的自己 用良心做教育

注册时间:2019-03-28

  • 博文量
    140
  • 访问量
    92334