前言
信号是事件发生时对进程的通知机制。也称之为软件中断
信号分两类:
①、用于内核向进程通知事件,构成标准信号。编号范围1~31。不支持排队处理。可供使用:SIGUSR1和SIGUSR2。不可靠信号。
②、由实时信号构成,编号范围32~63.采用SIGRTMIN+x 形式.支持排队(队列化管理)。信号编号越小,优先级越高,同级看接收时间先后。可靠信号。可携带数据信息
信号的处理:忽略、捕捉(设置处理程序)、默认行为
忽略:内核将信号丢弃,信号对进程没有产生影响,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)
捕捉(设置处理程序):设置一个某待捕捉的信号处理函数,当接受到该信号时,就会去处理对应的函数。
默认行为:每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
信号处理函数的注册:
①、signal——原始版
②、sigaction——首选版 //可收到数据信息
信号发送:
①、kill——原始版
②、sigqueue——首选版 //可发送信息
一、信号处理函数signal()
1、信号处理函数注册signal()
void (*signal(int sig, void (*func)(int)))(int);
等效于: 传入一个函数指针,返回一个函数指针。
typedef void (*sighandler_t)(int);//参数int是传入信号的编号,方便一个信号处理函数对应多个信号
sighandler_t signal(int sig, sighandler_t handler);
成功,返回之前的处置 失败:SIG_ERR
handler:信号处理函数
或者是SIG_IGN:忽略这个信号 SIG_DFL:恢复默认
signum:哪个信号
2、信号发送kill()
int kill(pid_t pid, int sig);
返回0成功
pid:进程id
sig:要发送的信号
pid > 0:将发送个该 pid 的进程
pid == 0:将会把信号发送给与发送进程属于同一进程组的所有进程,并且发送进程具有权限想这些进程发送信号。
pid < 0:将信号发送给进程组ID 为 pid 的绝对值得,并且发送进程具有权限向其发送信号的所有进程
pid == -1:将该信号发送给发送进程的有权限向他发送信号的所有进程。(不包括系统进程集中的进程)
3、练习demo.c
#include<signal.h>
#include<stdio.h>
#include <unistd.h>
static volatile int flag = 0;
//typedef void (*sighandler_t)(int);
void handler(int signum)
{
if(signum == SIGINT|| signum == SIGTERM)
{
flag = 1;
return;
}
if(signum == SIGIO)
printf("SIGIO signal: %d\n", signum);
else if(signum == SIGUSR1)
printf("SIGUSR1 signal: %d\n", signum);
else
printf("error\n");
}
int main(void)
{
//sighandler_t signal(int signum, sighandler_t handler);
signal(SIGIO, handler);
signal(SIGUSR1, handler);
printf("set handler signal:%d %d\n", SIGIO, SIGUSR1);
printf("receive a signal before\r\n");
while(!flag)
{
pause(); /*程序开始执行,就象进入了死循环一样,这是因为进程正在等待信号,当我们
按下Ctrl-C时(获接受到一个信号),信号被捕捉,并且使得pause退出等待状态。*/
printf("receive a signal \r\n");
}
return 0;
}
4、阻塞信号(信号掩码)sigprocmask()与等待状态信号sigpending()
阻塞信号:信号发出后,并不能直接被接收到(传递延后),处于阻塞状态,只能等解除阻塞或者设为忽略
等待状态信号:被设为阻塞的信号,被发出后就是处于等待状态信号
阻塞的设置和等待状态信号是通过状态字来记录的。分别为sigprocmask()和sigpending()
①、阻塞状态字获取和设置
int sigprocmask(int how, const sigset_t * set,
sigset_t * oldset);
success: 0 error: -1
how:
SIG_BLOCK:将set信号集添加到信号掩码里(并集 |)
SIG_UNBLOCK:将set信号集从信号掩码里移除(&)
SIG_SETMASK:将set信号集赋给信号掩码(=)
set:想要操作的信号集的数据对象
oldset:之前的信号集数据对象 如不需要可设为NULL
只可读取目前处于等待状态的信号
int sigpending(sigset_t *set);
success: 0 error: -1
set:存放返回的状态
该函数管理信号,是通过信号集的数据结构来进行管理的,信号集可以通过以下的函数进行管理。
②、信号集操作函数(状态字表示)
int sigemptyset(sigset_t *set); //初始化 set 中传入的信号集,清空其中所有信号
int sigfillset(sigset_t *set); //把信号集填1,让 set 包含所有的信号
int sigaddset(sigset_t *set, int signum);//把信号集对应位置为1
int sigdelset(sigset_t *set, int signum);//吧信号集对应位置为0
int sigismember(const sigset_t *set, int signum);//判断signal是否在信号集
设置/解除信号阻塞的步骤:
a、分配内存空间sigset sigset bset;
b、置空sigemptyset(&bset);
c、添加信号****sigaddset(&bset, SIGINT);
d、添加其他需要管理的信号…
e、设置信号集中的信号处理方案(此处为解除阻塞)sigprocmask(SIG_UNBLOCK, &bset, NULL);
5、等待信号pause()、raise()、killpg()
将暂停进程的执行,直到信号处理器函数中断该调用为止
int pause(void);
进程向自身发送信号
int raise(int sig);
向某一进程组的所以成员发送一个信号
int killpg(pid_t pgrp,int sig);
二、信号处理函数sigaction() 首选
sigaction是设置信号的另一种方法,与signal区别:
①、允许获得信号时无需将其改变
②、可携带额外信息数据(主要)
实时信号:SIGRTMIN~SIGRTMAX之间的信号,表示法:SIGRTMIN+x
实时信号可支持排队。设阻塞的信号,多次发该信号,并不是最终只发一次,而是将发出的信号排队处理,即能接受多个同信号。
1、信号处理函数注册sigaction()
int sigaction(int sig, const struct sigaction* act,
struct sigaction* oldact);
sucess: 0 error:-1
sig:获取或改变的信号编码(除SIGKILL和SIGSTOP)
act:信号新处置的对象,如仅对旧信号感兴趣可设为NULL
oldact:之前的信号处置,不意可NULL
struct sigaction {
union{
void (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
};
sigset_t sa_mask;/*阻塞关键字的信号集,可以在调用捕捉函数之前,把信号(包括自身
信号)添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。防止在执行处理函数时被自己后指定信号打断。*/
int sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
void (*sa_restorer)(void);//仅供内部使用
};
回调函数句柄sa_handler、sa_sigaction只能任选其一
sa_mask:设置执行处理函数时不可被中断的信号(包括自身);
sa_flags:处理过程选项: SA_SIGINFO(携带额外信息) SA_RESTART(自动重启中断的系统调用)
在处理函数的struct siginfo这个结构体主要适用于记录接收信号的一些相关信息。
sigval si_value这个成员中有保存了发送过来的信息;同时,在si_int或者si_ptr成员中也保存了对应的数据。
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
union sigval si_value; /* Signal value data from sigqueue */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
int si_band; /* Band event */
int si_fd; /* File descriptor */
}
union sigval {
int sival_int;
void *sival_ptr;
};
2、信号发送sigqueue()
使用前提:
①、制定了 SA_SIGINFO 的标志
②、sa_sigaction 成员提供了信号捕捉函数
int sigqueue(pid_t pid, int sig, const union sigval value);
pid:进程id
sig:信号编号
value:存放额外携带信息
union sigval {
int sival_int;
void *sival_ptr;
};
发送端:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#define MYSIG SIGRTMIN+5
int main(int argc,char *argv[])
{
int signo=MYSIG;
union sigval mysigval;
int i;
pid_t pid;
pid=(pid_t)atoi(argv[1]);
for(i=0;i<10;i++)
{
mysigval.sival_int=i;
//向指定进程发送一个信号和数据
printf("sigqueue signo=%d, mysigval.sival_int=%d\n",signo, mysigval.sival_int);
if(sigqueue(pid, signo, mysigval)==-1)
{
printf("sigqueue fail\n");
return -1;
}
sleep(1);
}
return 0;
}
3、练习demo.c
接受端
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#define MYSIG SIGRTMIN+5
int flag = 1;
//信号响应函数
void new_act(int signum, siginfo_t * info, void * myact)
{
printf("new_act=%d\n", signum);
printf("new_act->%d,%d\n", info->si_int,info->si_value.sival_int); /*从发送端传过来的数据,两个值相同*/
if(info->si_int == 9)
{
flag = 0;
}
}
int main(int argc,char *argv[])
{
int signo=MYSIG;
pid_t pid;
struct sigaction myAct; //指明对信号响应的设置信息
pid = getpid();// 调用getpid函数的进程是什么,就返回该进程的ID
printf("process id is %d,%d\r\n",pid, signo);
/*
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_sigaction成员而不是sa_handler作为信号处理函数。
myAct.sa_flags=SA_SIGINFO;
//指明信号响应函数
myAct.sa_sigaction=new_act;
//安装信号,为MYSIG设置响应函数
if(sigaction(signo, &myAct, NULL)<0)
{
printf("sigaction fail\n");
}
while(flag)
{
sleep(2);
printf("sleep \n");
}
return 0;
}
三、扩展定时SIGALRM
定时器产生的SIGALRM
函数原型:int setitimer(int which, const struct itimerval *new_value,
struct itimerval *old_value);
函数描述:计时器功能。
参数说明:
Which:表示类型,可选的值有:
ITIMER_REAL:以系统真实的时间来计算,它送出SIGALRM信号。
ITIMER_VIRTUAL:以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。
ITIMER_PROF:以该进程在用户态和内核态下所费的时间来计算,它送出SIGPROF信号。
new_value:用来对计时器进行设置
old_value:通常用不上,设置为NULL,它是用来存储上一次setitimer调用时设置的new_value值。
struct itimerval {
struct timeval it_interval; /* next value 第一次以后发出信号的时间间隔*/
struct timeval it_value; /* current value 第一次发出信号的时间间隔*/
};
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
itimeval又是由两个timeval结构体组成,timeval包含tv_sec和tv_usec两部分,其中tv_se为秒,tv_usec为微秒(即1/1000000秒)。
settimer工作机制是,先对it_value倒计时,当it_value为零时触发信号,然后重置为it_interval,继续对it_value倒计时,一直这样循环下去。
基于此机制,setitimer既可以用来延时执行,也可定时执行。
参考与引用:https://www.jianshu.com/p/f445bfeea40a