1、信号的基本概念
信号特点: 简单,不能携带大量信息,满足特定条件发生。信号也叫软件产生的中断,有可能会有延迟。
信号的机制: 进程B发送给进程A,内核产生信号,内核处理。
信号的产生:
- 按键产生,例如ctrl+c,ctrl+z,ctrl+\
- 调用函数 kill、raise、abort
kill函数:
原型:
#include <signal.h>
int kill(pid_t pid, int sig);
参数:
(1)pid>0,要发送的进程ID;pid=0,代表当前调用进程组内所有进程;pid=-1,代表有权限发送的所有进程;pid<0,代表-pid对应的组内所有进程。
(2)sig 对应的信号。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<signal.h>
int main()
{
int i;
for(i=0;i<5;i++)
{
pid_t pid = fork();
if(pid == 0)
{
break;
}
}
if(i == 2)
{
printf("I will kill father after 5s\n");
sleep(5);
kill(getppid(),SIGKILL); //kill函数使用
while (1)
{
sleep(1);
}
}
else if(i == 5)
{
//parent
while (1)
{
printf("I am parent,I am happy\n");
sleep(1);
}
}
return 0;
}
raise函数
作用:给当前进程发送指定信号,给自己发送信号。raise(signo) == kill(getpid(),signo)
#include <signal.h>
int raise(int sig);
返回值:成功,0;失败,非0。
abort函数
作用:给自己发送异常终止信号( 6)SIGABRT 信号),终止并产生core文件。
#include <stdlib.h>
void abort(void);
返回值:无返回值。
例子:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
#include<stdlib.h>
int main()
{
printf("I will die\n");
sleep(2);
raise(SIGKILL); //kill(getppid(),sig);
//abort();
return 0;
}
- 定时器 alarm、setitimer —时钟产生信号
alarm函数
作用:设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送 ( 14)SIGALRM信号)。进程收到该信号,默认动作终止。
每个进程都有且只有唯一个定时器。
原型:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
- 参数:几秒后发送信号。特别的,如果传入参数秒为0,代表取消闹钟。
- 返回值:返回0或剩余的秒数,无返回值表示失败。
常用:取消定时器 alarm(0) ,返回旧闹钟余下秒数。
#include<stdio.h>
#include<unistd.h>
int main()
{
int ret;
ret = alarm(6); //设置6s定时器
printf("ret = %d\n",ret);
sleep(2); //sleep 2s后,消耗2s时间
ret = alarm(5); //再重新设定5s定时器
printf("ret = %d\n",ret);
while (1)
{
printf("I will be kill by alarm!\n");
sleep(1);
}
return 0;
}
输出:
~/Linux_C$ ./alarm_
ret = 0
ret = 4
I will be kill by alarm!
I will be kill by alarm!
I will be kill by alarm!
I will be kill by alarm!
I will be kill by alarm!
Alarm clock
**
##NOTE:重新设定定时器,返回上一个定时器剩余时间,继续执行新定时器的设定。
**
- 命令产生 kill
- 硬件异常、段错误、浮点型错误、总线错误、SIGPIPE
信号的状态: - 产生
- 递达,信号达到并且处理完
- 未决,信号被阻塞了。产生和递达之间的状态,主要由于阻塞(屏蔽)导致该状态。
信号的默认处理方式: - 忽略(丢弃)
- 执行默认动作
- 捕获。9、19号信号不能捕获,不能忽略,甚至不能阻塞。
信号的4要素: (可通过 man 7 signal 查看帮助文档获取,也可以查看/usr/src/linux-headers-4.15.0-142/arch/s390/include/uapi/asm/signal.h ) - 编号
- 事件
- 名称
- 默认处理动作
忽略、终止、终止+core、暂停、继续
2、信号相关函数/参考文档使用信号集操作相关函数
(1) setitimer函数
作用: 周期性地发送信号。
#include <sys/time.h>
//int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,
struct itimerval *old_value);
参数:
- which
(1)ITIMER_REAL 自然定时法,SIGALRM
(2)ITIMER_VIRTUAL 计算进程执行时间,SIGVTALRM
(3)ITIMER_PROF 进程执行时间+调度时间,ITIMER_VIRTUAL - 第2、 3个参数为结构体,new_value要设置的闹钟时间,old_value原闹钟时间(可以用于恢复)
struct itimerval {
struct timeval it_interval; 周期性的时间设置/* Interval for periodic timer */
struct timeval it_value; 下次的闹钟时间/* Time until next expiration */
};
struct timeval {
time_t tv_sec; 秒 /* seconds */
suseconds_t tv_usec; 微妙 /* microseconds */
};
- 返回值:成功0,失败-1。On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
3、信号捕捉函数signal、sigaction
(1)信号捕捉基本概念
捕捉:调用户处理函数。
Linux内核的进程控制块PCB是一个结构体,task_struct,除了包含进程id、状态、工作目录、用户id、组id、文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。
阻塞信号集(信号屏蔽字): 将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(解除屏蔽后再处理)。
未决信号集:
- 信号产生,未决信号集中描述该信号的位,立刻翻转为1,表示信号处于未决状态。当信号被处理对应位翻转回为0。这一刻往往非常短暂。
- 信号产生后由于某些原因(主要是阻塞)不能抵达,这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。
信号捕捉特性:
1、进程正常运行时,默认PCB中有一个信号屏蔽字,假定为 * ,他决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由 * 来指定,而是用sa_mask来指定。调用完信号处理函数,再恢复为 * 。
2、xxx 信号捕捉函数执行期间,xxx信号自动被屏蔽。
3、阻塞的常规信号不支持排队,产生多次只记录一次。(后32个实时信号支持排队)
(2)信号捕捉函数
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
sigemptyset 函数
清空信号集
sigfillset 函数
填充信号集
sigaddset 函数
添加某个信号到信号集
sigdelset 函数
从信号集中删除某个信号
sigismember 函数
判断信号是否为集合里的成员,sigismember () 函数返回1代表signum在集合中。
sigprocmask() 函数
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
作用:设置阻塞或解除阻塞信号集,用来屏蔽信号、解除屏蔽也使用该函数,其本质为读取或修改进程的信号屏蔽字(PCB中)。
严格注意,屏蔽信号,只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢弃处理。
参数:
set 传入参数,是一个位图,set中哪位置为 1,就表示当前进程屏蔽哪个信号。
oldset 传出参数,保存旧的信号屏蔽集。
how 参数取值,假设当前的信号屏蔽字为mask
- SIG_BLOCK: 设置阻塞,当how设置为此值,set 表示需要屏蔽的信号,相当于mask = mask|set。
- SIG_UNBLOCK:解除阻塞,当how设置为此值,set 表示需要解除屏蔽的信号,相当于 mask = mask & set。
- SIG_SETMASK:设置set为新的阻塞信号集,当how设置为此值,set 表示用于代替原始屏蔽集的新屏蔽集。相当于mask = set,调用sigprocmask解除了对当前若干个信号的阻塞。
返回值:成功 0,失败 -1,设置errno。
sigpending() 函数
#include <signal.h>
int sigpending(sigset_t *set);
作用:读取当前进程的未决信号集。参数:set 传出参数。返回值:成功 0,失败 -1,设置errno。
练习:编写程序,把所有常规信号的未决状态打印至屏幕。
//获取常规信号的未决信号集
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
int main()
{
sigset_t pend;
sigpending(&pend);
int i;
for(i=1; i<32; i++)
{
if(sigismember(&pend,i) == 1)
{
printf("1");
}
else
{
printf("0");
}
}
printf("\n");
return 0;
}
//获取常规信号的未决信号集
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
int main()
{
sigset_t pend,sigproc;
//设置阻塞信号,等待按键产生信号
sigemptyset(&sigproc); //先清空
sigaddset(&sigproc,SIGINT);
sigaddset(&sigproc,SIGQUIT);
sigaddset(&sigproc,SIGKILL); //9号信号不可被阻塞
//设置阻塞信号集
sigprocmask(SIG_BLOCK,&sigproc,NULL);
//循环取未决信号集,打印
while (1)
{
sigpending(&pend);
int i;
for(i=1; i<32; i++)
{
if(sigismember(&pend,i) == 1)
{
printf("1");
}
else
{
printf("0");
}
}
printf("\n");
}
return 0;
}
运行程序,会一直打印未决信号集,ctrl+c 会发现第二位信号变为1。
0100000000000000000000000000000
signal() --信号捕捉函数
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
作用:注册一个信号捕捉函数。
该函数由ANSI定义,由于历史原因在不同版本的UNIX和不同版本的linux中可能有不同的行为,因此应尽量避免使用,使用sigaction() 代替。
sigaction() --信号捕捉函数
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
参数:
- signum 捕捉的信号
- act 传入的动作,是一个结构体类型指针
struct sigaction {
void (*sa_handler)(int); //函数指针
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask; //执行捕捉函数期间,临时屏蔽的信号集
int sa_flags; //一般填0,SA_SIGINFO 会使用第二个函数指针
void (*sa_restorer)(void); //无效
};
- oldact 原动作,恢复现场。