1、信号的基本概念

1、 信号(signal)是软件中断,是进程之间相互传递信息的一种方法,用于通知进程发送了的事件,但是,不能给进程传递任何数据 2、信号产生的原因有很多,在Linux下,可以用kill和killall命令发送信号

ps -ef | grep 需要查询的程序
kill 跟随的是进程号
killall 程序名

一共有64个信号,每个信号对应自己的默认动作

Linux C 信号使用_#include

在这里插入图片描述

进程状态表

Linux C 信号使用_#include_02

在这里插入图片描述

信号名 信号值默认处理动作发出信号的原因
SIGHUP 1 A 终端挂起或者控制进程终止
SIGINT 2 A 键盘中断Ctrl+c
SIGQUIT 3 C 键盘的退出键被按下
SIGILL 4 C 非法指令
SIGABRT 6 C 由abort(3)发出的退出指令
SIGFPE 8 C 浮点异常
SIGKILL 9 AEF 采用kill -9进程编号强制杀死程序。
SIGSEGV 11 C 无效的内存引用
SIGPIPE 13 A 管道破裂,写一个没有读端口的管道。
SIGALRM 14 A 由alarm(2)发出的信号
SIGTERM 15 A 采用“kill 进程编号”或“killall 程序名”通知程序。
SIGUSR1 10 A 用户自定义信号1
SIGUSR2 12 A 用户自定义信号2
SIGCHLD 17 B 子进程结束信号
SIGCONT 18进程继续(曾被停止的进程)
SIGSTOP 19 DEF 终止进程
SIGTSTP 20 D 控制终端(tty)上按下停止键
SIGTTIN 21 D 后台进程企图从控制终端读
SIGTTOU 22 D 后台进程企图从控制终端写

处理动作一项中的字母含义如下:
A 缺省的动作是终止进程。
B 缺省的动作是忽略此信号,将该信号丢弃,不做处理。
C 缺省的动作是终止进程并进行内核映像转储(core dump),内核映像转储是指将进程数据在内存的映像和进程在内核结构中的部分内容以一定格式转储到文件系统,并且进程退出执行,这样做的好处是为程序员提供了方便,使得他们可以得到进程当时执行时的数据值,允许他们确定转储的原因,并且可以调试他们的程序。
D 缺省的动作是停止进程,进入停止状态的程序还能重新继续,一般是在调试的过程中。
E 信号不能被捕获。
F 信号不能被忽略。

2、两个信号捕获函数

2.1、signal信号函数

Linux C 信号使用_回调函数_03

在这里插入图片描述

// 函数指针:返回值void,有一个int参数
typedef void (*sighandler_t)(int);

// 主要用于捕获信号,交给回调函数处理
sighandler_t signal(int signo,sighandler_t handler);
参数:
    signo 信号编号
    handler 回调函数(返回值void参数为int的函数类型)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>

voidfunc(int sig)
{
printf("成功捕获%d信号\n",sig);
}

intmain()
{
// 捕获所有信号,只要有信号来就调回调函数,也可以指定特定信号
for(int i=1;i<=64;i++)
        signal(i,func);
while(1)
{
printf("执行一次任务。\n");
        sleep(1);
}
return0;
}
// 忽略信号
    signal(SIGTERM,SIG_IGN);
    //还原成系统缺损的动作
    signal(SIGTERM,SIG_DFL);

2.2、sigaction函数

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
参数:
    signum 信号编号
    act 默认需要初始化结构体
    oldact 旧的结构体(一步不使用可以传NULL)

返回值:
    成功 0
    失败 -1
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>

voidsys_perror(const char* str)
{
    perror(str);
exit(1);
}

voidcat_sig(int signo)
{
printf("hello world %d\n",signo);
}

intmain()
{
struct sigaction act,oldact;
    act.sa_handler = cat_sig;// 设置信号捕捉函数
    sigemptyset(&(act.sa_mask));// 设置信号捕捉期间mask
    act.sa_flags =0;// 设置默认属性

int ret = sigaction(SIGINT,&act,&oldact);
if(ret ==-1)
        sys_perror("sigaction error");
// signal(SIGINT,cat_sig);
while(1);
return0;
}

2.3、信号相关函数

sigemptyset() 把信号集中所有信号都置为0,表示所有信号都没有来
sigfillset()把信号集中所有信号都置为1,
sigaddset()增加信号,把某个信号从0变成1
sigdelset()删除信号,把某个信号从1变成0
sigprocmask()设置信号集中的内容
sigismember()测试当前信号在信号集中是否为1返回true
SIG_BLOCK:
按照参数set提供的屏蔽字,屏蔽信号。并将原信号屏蔽保存到oldset中。
SIG_UNBLOCK:
按照参数set提供的屏蔽字进行信号的解除屏蔽。针对Set中的信号进行解屏。
SIG_SETMASK:
按照参数set提供的信号设置重新设置系统信号设置。

一个进程会对应一个信号集,用来记录阻塞了哪些信号,如果把这个信号集中的某个信号位设置位1,
此时在来同类信号就会阻塞
#include <iostream>
#include <signal.h>
#include <unistd.h>
usingnamespace std;

void mysignal(int sig)
{
printf("信号:%d\n",sig);
signal(sig,SIG_DFL);// 执行一次后,恢复默认行为
}

int main()
{
sigset_t newsig,oldsig;
signal(SIGQUIT,mysignal);// 注册信号回调函数
sigemptyset(&newsig);// 将newsig信号集中所有信号设置为0
sigaddset(&newsig,SIGQUIT);// 将newsig信号集中的SIGQUIT信号设置为1
// 设置当前进程的信号集
// 第一个参数SIG_BLOCK表示设置进程新的信号屏蔽字(当前信号和第二个的并集)
// 信号集
// 不为空,则保存信号之前的信号集
sigprocmask(SIG_BLOCK,&newsig,&oldsig);
sleep(5);
if(sigismember(&newsig,SIGQUIT))// 测试当前信号的信号集是否是1
{
printf("SIGQUIT信号已经被屏蔽\n");
}

if(sigismember(&newsig,SIGHUP))// 测试当前信号的信号集是否是1
{
printf("SIGHUP信号已经被屏蔽\n");
}

sigprocmask(SIG_SETMASK,&oldsig,NULL);
if(sigismember(&oldsig,SIGQUIT))// 测试当前信号的信号集是否是1
{
printf("SIGQUIT信号已经被屏蔽\n");
}
else
{
printf("当前SIGQUIT已解除屏蔽\n");
}
sleep(5);
printf("结束\n");
return0;
}

3、两个时钟函数

3.1、alarm函数

(定时发送SIGALRM给当前进程,默认动作为终止进程,只会执行一次)

unsigned int alarm(unsigned int seconds);
参数:
    seconds 定时秒数
返回值:
    上次定时闹钟剩余时间
    无错误现象

SIGALRM信号默认动作是终止进程,现在我们捕获了这个进程,然后用自定义函数处理

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>

voidalarmfunc(int sig)
{
printf("接收到了时钟信号%d\n",sig);
}

intmain()
{
    signal(SIGALRM,alarmfunc);
    alarm(3);
while(1)
{
printf("执行一次任务。\n");
        sleep(1);
}
return0;
}

3.2、setitimer函数

(定时发送SIGALRM给当前进程,默认动作为终止进程)

int setitimer(int which,const struct itimerval* new_value,struct itimerval* old_value);
参数:
    which 
        ITIMER_REAL (14 SIGLARM)自然时间
        ITIMER_VIRTUAL(26 SIGVTALRM)虚拟空间时间(只计算进程占用cpu的时间)
        ITIMER_PROF (27 SIGPROF)运行时计时(用户+内核,)
    new_value 定时秒数
        it_interval:用来设定两次定时任务之间间隔的时间
        it_value:定时的时长
    old_value 上次定时闹钟剩余秒数
返回值:
成功0
失败 -1
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>

voidsys_perror(const char* str)
{
    perror(str);
exit(1);
}

intmain()
{// 参数初始化
struct itimerval it,oldit;
    it.it_value.tv_sec =1;// 设置秒说
    it.it_value.tv_usec =0;

    it.it_interval.tv_sec =0;// 设置间隔为0秒
    it.it_interval.tv_usec =0;

// 错误检查
if(setitimer(ITIMER_REAL,&it,&oldit)==-1)
        sys_perror("setitimer error");

int i=1;
while(i++)
{
printf("%d\n",i);
}
return0;
}

4、信号作用

服务程序运行在后台,如果想让终止它,直接杀掉,不是很好,因为程序突然死亡,很多工作没有处理,如果想服务程序发送一个信号,服务程序收到这个信号后,调用一个函数,在函数中编写善后代码,程序就可以有计划的退出,向服务程序发送0的信号,可以检测释放存活

5、可重入函数

Linux C 信号使用_插入图片_04

在这里插入图片描述

所谓可重入函数:就是我们在信号处理函数中,调用是安全的,这些函数是可重入的并称为异步信号安全的。 如果必须要在信号处理函数中调用那些可能修改errno值的可重入的系统函数,就需要事先备份,然后在信号函数尾部给他恢复回去。