Linux——信号及其使用

信号的基本概念

信号是系统响应某个条件而产生的事件,进程接受到信号会执行相应的操作。(软中断信号,用来通知进程发生了异步事件)
信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达。
系统预先定义好的某些特定事件,信号可以被发送,也可以被接受,发送和接受的主题都是进程。

可靠信号以及不可靠信号

  • 不可靠信号:信号值小于SIGRTMIN(32)的信号,主要存在的问题是:进程每次处理信号后,就将对信号的响应设置为默认动作,在某些情况下,将导致信号的错误处理,用户如果不希望这样操作,就将在信号处理函数的结尾再一次调用signal()重新安装该信号,(总的俩说就是进程可能对信号作出错误的反应,以及信号可能丢失)
  • 可靠信号:在原有信号的机制上进行改进和扩充,这些信号支持派对,不会丢失,信号的发送和安装也出现了新函数(sigqueue和sigaction),早期的kill和signal依然被支持,信号值位于SIGRTMIN(32)和SIGRTMAX(63)之间的都称为可靠信号。

注意: 信号的可靠与不可靠只与信号值有关,与信号的发送和安装函数无关
signal和sigaction函数的主要区别就是:经过sigaction函数安装的信号都能传递信息给信号处理函数(对所有信号都成立),而经过signal函数安装的信号却不能向信号处理函数传递信息,对于信号发送函数也是一样的。

与信号有关的系统调用在“signal.h”头文件中有声明
常见信号的值,及对应的功能说明:
在这里插入图片描述

在Linux系统下定义了一些信号:
存储位置为:
/usr/include/bits/signum.h 在这里插入图片描述

理解信号:通知进程产生了某事件
在这里插入图片描述

忽略:有两个信号不可忽略:SIGKILL和SIGSTOP,
默认:linux对每一个信号对规定了默认操作,对实时信号(后32)的默认反应是进程中止。
自定义:定义信号处理函数,信号发生时,执行响应的信号处理函数。

信号的使用

信号发送
1、kill函数

将参数sig指定的信号传递给参数pid指定的进程,pid有如下几种情况:
①pid > 0:将信号传给进程识别码为pid的进程;
②pid = 0:将信号传给和目前进程相同进程组的所有进程;
③pid = -1:将信号广播给系统内的所有进程;
④pid < 0:将信号传给进程组识别码为pid绝对值的所有进程;

#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int sig)

执行成功返回0,执行失败返回-1;

2、alarm函数

用于设置信号传送闹钟,用来设置信号SIGALRM,在经过参数seconds指定的秒数后传送给目前的进程,若参数0,则之前设置的闹钟会被取消,并将剩下的时间返回。

#include<unistd.h>
unsigned int alarm(unsigned int seconds);

示例

int main()
{
	int i;
	signal(SIGALRM,handler);
	alarm(5);
	for(i = 1;i < 7;++i)
	{
		printf("sleep %d...\n",i);
		sleep(1);
	}
}
自定义信号处理方式

如果信号要处理某一个信号,首先就要在进程中安装该信号。安装信号主要用来确定要处理哪个信号以及当信号被传递给进程时,将执行何种操作。

signal()

—— 不支持信号传递信息,主要用于前32中非实时信号的安装(有两个参数)

  • 默认SIG_DFL
  • 忽略SIG_IGN
  • 自定义void fun_sig(int sig)

我们通过帮助手册了解一下在这里插入图片描述

  • 第一个参数是一个信号代号
  • 第二个参数是响应方式,是一个函数指针,返回值为void,参数为int

在这里插入图片描述
对于上面的程序,只有在按下ctrl+c后才会结束,否则将会一直执行下去,原因是给主程序发送了一个2号信号SIGINT,即终端终端信号,程序收到该信号后自动退出。
接下来,我们改变以下对于该信号的响应方式,我们收到该信号后打印一下这个信号的代号:
代码如下:

注意:这里的signal并没有执行,只是一个声明,只有当收到信号时才会调用
int sig就是信号的代号
在这里插入图片描述
此时我们按下Ctrl+c后发现是这样的:主程序并没有被打断,原因是:主程序是死循环是不会退出的,如果收到信号,当前程序被打断,内核帮助我们调用fun方法,再恢复执行while的函数体内容,因为我们已经改变了信号的响应方式,之前是按照默认方式响应的。
在这里插入图片描述此时我们使用**Ctrl+\ **强制退出结束

下面展示对于该信号的三种操作方式的写法:

//默认
signal(SIGINT,SIG_DFL);
//忽略
signal(SIGINT,SIG_IGN);
//自定义
signal(SIGINT,sig_fun);

思考一下,如何使得第一次产生信号时是自定义,第二次又调用默认呢?
修改一下自定义函数即可:
在这里插入图片描述
于是:

在这里插入图片描述

siigaction()

—— 支持信号传递信息,主要用于前32中非实时信号的安装(有三个参数)
函数原型

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

根据参数signum指定的信号编号来设置该信号的处理函数,如果参数oldact不是NULL,则原来的信号处理方式将通过结构sigaction返回。

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
}
  • sa_handler是一个函数指针此参数和signal()的参数handler相同,代表新的信号处理函数
  • sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置
  • sa_flags 用来设置信号处理的其他相关操作,下列的数值可用。
  • SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
  • SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
  • SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号

sa_sigaction 则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。当 sa_flags 成员的值

包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理

函数。在某些系统中,成员 sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。
sa_mask 成员用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被

自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
 
int main()
{
    struct sigaction newact,oldact;
 
    /* 设置信号忽略 */
    newact.sa_handler = SIG_IGN; //这个地方也可以是函数
    sigemptyset(&newact.sa_mask);
    newact.sa_flags = 0;
    int count = 0;
    pid_t pid = 0;
 
    sigaction(SIGINT,&newact,&oldact);//原来的备份到oldact里面
 
    pid = fork();
    if(pid == 0)
    {
        while(1)
        {
            printf("I'm child gaga.......\n");
            sleep(1);
        }
        return 0;
    }
 
    while(1)
    {
        if(count++ > 3)
        {
            sigaction(SIGINT,&oldact,NULL);  //备份回来
            printf("pid = %d\n",pid);
            kill(pid,SIGKILL); //父进程发信号,来杀死子进程
        }
 
        printf("I am father .......... hahaha\n");
        sleep(1);
    }
 
    return 0;
}

在这里插入图片描述

信号集操作

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

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

x信号集用来描述信号的集合,Linux所支持的所有信号可以全部或部分地出现在信号集中,主要与信号阻塞相关的函数配合使用,以下是一些信号集操作定义的函数:

①sigemptyset函数

用于初始化信号集

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

用于将参数set初始化,将所有信号加入此信号集

int sigfillset(sigset_t *set);            
③sigaddset函数

用于将参数signum代表的信号加入到参数set中

int sigaddset(sigset_t *set, int signum);  
④sigdelset函数

用于从set信号集中删除signum信号

 int sigdelset(sigset_t *set, int signum);    
⑥sigismember函数

用于测试某个信号是否已加入信号集中

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

用信号处理僵尸进程

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <assert.h>

void fun(int sign)
{
    wait(NULL);
}
int main()
{
    signal(SIGCHILD,fun);//绑定信号与信号处理函数
    pid_t t = fork();
    assert(t!=-1);
    
    if(t == 0)
    {
        printf("child start\n");
        sleep(2);
        printf("child over");
	}
    else
    {
        int i=0;
        while(i<100)
        {
            sleep(1);
            i++;
		}
    }
}

例:运行程序,第一次接收到SIGINT信号,进程打印HelloWorld,第二次接受SIGINT信号,进程结束
方法:

  1. 第一次接受信号:fun
  2. 第二次接受信号前,将信号的响应方式修改为默认SIG_DFL
  3. 第二次接收信号,默认退出程序
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void fun(int sign)
{
    printf("HelloWorld\n");//第一次接收到信号
    signal(SIGINT,SIG_DFL);//将信号响应方式恢复为默认,默认退出程序
}
int main()
{
    signal(SIGINT, fun);//程序启动时,绑定
    int i=0;
    while(i<100)
    {
        sleep(1);
        printf("process running...\n");
        i++;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值