专栏首页程序员Linux进程通信——信号

Linux进程通信——信号

版权声明:本文为博主原创文章,转载请注明博客地址: https://blog.csdn.net/zy010101/article/details/83931740

信号是在软件层面对中断机制的一种模拟,信号的出现使得进程直接的通信不在是被动的,不在向之前那样,read()操作往往需要等待write()操作结束。因为信号是对中断的一种模拟。既然是中断,那么它的发生就是不确定。就不会发生一个进程阻塞在这里等待另一个进程执行的结果。这样的异步性通信机制无疑是更加强大的。

在终端输入kill -l可以查看当前系统所支持的所有信号。(我这个是Ubuntu)

可以看到有64个信号,其中有两个较为特殊的信号是SIGRTMIN和SIGRTMAX。Linux下的通信机制是遵从POSIX标准的。34号信号SIGRTMIN信号之前的是早期UNIX操作系统的。它们是不可靠的信号。它的主要问题是:进程每次处理信号后,会设置对该信号的默认处理动作,有时候我们不想让他这么处理了(按照默认处理),这时候就需要调用signal()函数重新安装一次信号。这样会形成新的默认动作。还有更加讨厌的是,信号有可能会丢失。

Linux对不可靠信号做了一些改进,现在的主要问题变成了“信号会丢失”。

后来POSIX仅仅只对可靠信号做了标准化。信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号。可靠信号它不会丢失。

可靠信号都是实时信号,不可靠信号都是非实时信号。可靠信号都支持队列处理,不可靠信号不支持队列处理。在UNIX时代就定义好了前面的不可靠信号的功能,而后来增加的可靠信号是让用户自定义使用的。

信号处理的三种方式:

  1. 忽略信号:对信号不做任何处理,就当做没发生任何事情一样。(SIGKILL和SIGSTOP这两个不能忽略)
  2. 捕捉信号:定义信号处理函数,当信号发出的时候,执行相应的操作。(这个和Qt的信号槽差不多)
  3. 执行默认动作:Linux对每一个信号都规定了默认操作(可靠信号的默认操作是进程终止)。

发送信号

发送信号的函数有kill(),raise(),sigqueue(),alarm(),setittimer(),abort()。常用的是kill()。它们依赖的头文件是#include<signal.h>和#include<sys/types.h>

函数原型:int kill(pid_t pid,int sig);

函数功能:用来将sig所指定的信号发送到pid所指定的进程。

pid有下面几种情形,分别对应于不同情况下应用。

  • pid > 0:把信号传递到进程ID为pid的进程
  • pid == 0:把信号传送给当前进程所在组的所有进程
  • pid == -1:将信号以广播的形式传送给系统内所有进程
  • pid < -1: 讲信号传递给进程组识别码为pid绝对值的所有进程

函数执行成功返回0,否则返回-1.

测试代码如下:

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

int main()
{
    pid_t pid;
    int statu;
    pid = fork();
    if(0 == pid)
    {
        printf("son\n");
        sleep(5);
        printf("I am son!\n");
        exit(0);
    }
    if(0 < pid)
    {
        sleep(2);
        printf("father\n");
        kill(pid,SIGABRT);      //SIGABRT是终止子进程
        wait(NULL);
        printf("My son GG\n");
        exit(0);
    }
    return 0;
}

让子进程先执行,打印出son。然后让子进程挂起。轮到父进程执行,父进程执行到kill()函数的时候给子进程发了个SIGABRT信号,让子进程终止了。然后wait()回收子进程,打印My son GG.

执行结果如下:

可以看到,子进程收到SIGABRT信号后,终止了。没有向屏幕打印I am son.关于信号的详解,看这里:https://blog.csdn.net/zy010101/article/details/83932113

上面的kill函数发送的信号是不可靠信号,它执行默认操作。即:终止进程。如果我们需要自定义信号处理方式,那么就需要安装信号。Linux安装信号主要由signal()和sigaction()完成。signal是在可靠信号系统调用的基础上实现的,是库函数。

signal()的原型很复杂,我们还是从signal.h这个头文件来看一下吧!

extern __sighandler_t signal (int __sig, __sighandler_t __handler)

__THROW;

可以看到signal有两个参数,一个是信号值,另一个我们再来看看

typedef void (*__sighandler_t) (int);

另一个是这样的一个函数指针变量,那么说明__handler代表了一个函数的入口地址(实际就是函数)。另外,这个函数指针指向的函数需要一个int类型的参数。signal函数的返回值也是一个函数指针。

注意:__handler如果不是函数指针,它只能是SIG_IGN或者是SIG_DFL.

SIG_IGN:忽略参数指定的信号。(忽略该信号)

SIG_DFL:将参数指定的信号重新设置为内核默认的处理方式。

返回值:signal函数本身在成功时返回NULL,它的参数__handler则会返回处理信号的函数的地址(函数指针)。失败返回:SIG_ERR.

所以这就要求自定义的信号处理函数的函数原型是这样的:

void 函数名(int 参数名);即:函数必须有一个int类型的参数。

signal()函数只是定义了将指定信号传送到指定进程。还需要一个用于捕捉信号的函数。在Linux下pause()函数用于捕捉信号,如果没有信号发生,pause函数将会一直等待。直到有信号发生。

函数原型:int pause();

当pause函数捕捉到信号的时候返回-1(注意不是捕捉到的信号的值)。

测试程序如下:

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

//自定义的信号处理函数
void My_Fun(int sig)
{
    if(SIGRTMIN == sig)
    {
        printf("MIN!\n");
    }
    if(SIGRTMAX == sig)
    {
        printf("MAX!\n");
    }
}

int main()
{
    //注册信号处理函数
    signal(SIGRTMIN,My_Fun);
    signal(SIGRTMAX,My_Fun);
    //挂起10s
    sleep(3);
    //发出信号
    kill(getpid(),SIGRTMIN);    //getpid()函数用于获取当前进程的pid.
    kill(getpid(),SIGRTMAX);
    return 0;
}

输出结果如下:

这样就完成了自定义信号的使用。使用自定义信号有两个关键点。一是必须注册自定义信号的处理函数,二是必须发送自定义信号。怎么样发送自定义信号由你自己来定义,这为程序设计带来了极大的便利。比如上面我们只是直接了当的发送两个信号。你也可以使当满足一定条件的时候才发送信号。比如下面这样。

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

//自定义的信号处理函数
void My_Fun(int sig)
{
    if(SIGRTMIN == sig)
    {
        printf("MIN!\n");
    }
    if(SIGRTMAX == sig)
    {
        printf("MAX!\n");
    }
}

int main()
{
    //注册信号处理函数
    signal(SIGRTMIN,My_Fun);
    signal(SIGRTMAX,My_Fun);
    //发出信号
    char c;
    while(1)
    {
        scanf("%c",&c);
        getchar();                      //吸收回车
        if('a' == c)
        {
            kill(getpid(),SIGRTMIN);    //getpid()函数用于获取当前进程的pid.
        }
        else
        {
            kill(getpid(),SIGRTMAX);
        }
    }

    return 0;
}

运行结果如下:

这样就实现了发送信号的控制。可以想象,键盘,鼠标等发送的信号很有可能就是被系统采取这样的方式处理的。

另外一个函数是sigaction()函数。

函数原型:int sigaction(int sig,const struct sigaction *newact,const sigaction *oldact);

函数功能:sigaction函数根据参数sig指定的信号来处理信号。参数可以是SIGKILL和SIGSTOP以为的其他信号。newact是新的信号处理方式,oldact是旧的信号处理方式。这个结构体包含如下成员

struct sigaction()
{
    void(*sa_handler)(int);        //指向信号处理函数的函数指针
    sigset_t sa_mask;              //用来设置处理该信号的时候暂时屏蔽sa_mask指定的信号
    int sa_flags;                  //设置信号处理的方式
    int (*sa_restorer)(void);      //输出参数, 指向struct sigaction 结构的指针
}

在C语言里结构体的名字可以和函数名相同。(其实是C语言的结构体名称应该是struct xxx。带上关键字才是真正的结构体名)。

信号集

信号集被定义为一种数据类型:

typedef struct
{
    unsigned long sig[_NSIG_WORDS];
}sigset_t;

信号集主要配合一下的信号阻塞函数来使用。

  1. sigemptyset()函数: 函数原型:int sigemptyset(sigset_t *set); 函数功能:用来将set信号集给初始化并清空。
  2. sigaddset()函数: 函数原型:int sigaddset(sigset_t *set,int sig); 函数功能:将参数sig指定的信号加入信号集set。
  3. sigfillset()函数: 函数原型:int sigfillset(sigset_t *set); 函数功能:把所有信号加入到set中。
  4. sigdelset()函数: 函数原型:int sigdelset(sigset_t *set,ing sig); 函数功能:将参数sig指定的信号从set中删除

使用信号注意的问题:

  1. 注意信号是否会丢失这个问题,尽量使用可靠信号。
  2. 注意信号的可移植性,POSIX标准指定的信号函数和信号
  3. 信号处理函数应当是一个可重入函数。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Linux信号列表及其详解

    列表中,编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号...

    zy010101
  • 大端模式和小端模式

    一般在计算机中数据指针取到的都是该数据存储的起始位置的地址。比如 int a;它在32位下占据4字节。现在有一个int *p = &a;那么将会取到该数据在内...

    zy010101
  • OpenCV中的createTrackbar函数

    在OpenCV中,我们常常需要调节卷积核的大小(邻域的大小)或者是亮度,对比度的调整。这时候如果我们通过手动修改是非常麻烦的。这时候使用OpenCV提供的cre...

    zy010101
  • DTCoreText的集成与使用目录一、相关资源二、DTCoreText的集成三、DTCoreText的使用四、可能遇到的错误五、参考链接

    DTCoreText是可以将HTML字符串转化为富文本使用的工具,既保证原生实现又能适应灵活的样式修改,而且相比于使用WebView显示内容在性能上也有很大优势...

    梧雨北辰
  • gps信号发生器在某汽车公司的应用方案

    GPS信号发生器在某汽车公司成功投运,为该gps信号发生器提供进行选配惯导仿真组件,可同时模拟GPS定位导航授时信号,用于组合导航接收的研发、生成、检定。同时也...

    时频专家
  • JNDI:如同胶水

    上篇文章写的关于tomcat数据库连接池的配置。在对连接池就行操作的时候使用到了传说中的JNDI技术。

    the5fire
  • 使用 Moq 测试.NET Core 应用 -- Mock 属性

    第一篇文章, 关于Mock的概念介绍: https://www.cnblogs.com/cgzl/p/9294431.html

    solenovex
  • Go netpoll I/O 多路复用构建原生网络模型之源码深度解析

    Go 基于 I/O multiplexing 和 goroutine 构建了一个简洁而高性能的原生网络模型(基于 Go 的I/O 多路复用 netpoll),提...

    潘少
  • Python文件处理入门篇

    昨天分享了一个关于文件搜索的小实战项目,其实文件处理是Python里面非常重要的一块内容,知识点很多,Python对本地文件的处理,主要是通过文件的读和写来完成...

    stormwen
  • 《Walk On LuaJIT》 (上篇)

    概要:经历了一段时间的打磨,Tweyseo老师在Lua.ren首发了他的新文章《Walk On LuaJIT》。因为微信公众号对群发文章有字数限制,我们把文章分...

    糖果

扫码关注云+社区

领取腾讯云代金券