信号
概述
信号是软件中断,它是在软件层次上对中断机制的一种模拟。
信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。 信号是一种异步通信方式。
进程不必等待信号的到达,进程也不知道信号什么时候到达。 信号可以直接进行用户空间进程和内核空间进程的交互,内核进程可以利用它来通知用户空间进程发生了哪些系统事件
信号和编码的对应关系: kill -l
如何产生信号
1、当用户按某些终端键时,将产生信号。
2、硬件异常将产生信号。
3、软件异常将产生信号。
4、调用 kill 函数将发送信号。
5、运行 kill 命令将发送信号。
一个进程收到一个信号的时候,可以用如下方法进行处理:
1、执行系统默认动作 对大多数信号来说,系统默认动作是用来终止该进程。
2、忽略此信号 接收到此信号后没有任何动作。
3、执行自定义信号处理函数 用用户定义的信号处理函数处理该信号。
信号的使用
1.kill 函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int signum);
功能:给指定进程发送信号。
参数:
pid:详见下页
signum:信号的编号
返回值:成功返回 0,失败返回 -1。
pid 的取值有 4 种情况:
pid>0: 将信号传送给进程 ID 为 pid 的进程。
pid=0: 将信号传送给当前进程所在进程组中的所有进程。
pid=-1: 将信号传送给系统内所有的进程。
pid<-1: 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。
注意:使用 kill 函数发送信号,接收信号进程和发送信号进程的所有者必须相同,或者发送信号进程的所有者是超级用户。
2.alarm 函数
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:在 seconds 秒后,向调用进程发送一个 SIGALRM 信号,SIGALRM 信号的默认动作是终止调用 alarm 函数的进程。
返回值:
若以前没有设置过定时器,或设置的定时器已超时,返回 0;否则返回定时器剩余的秒数,并重新定定时器。
raise 函数
#include <signal.h>
int raise(int signum);
功能:给调用进程本身送一个信号。
参数:
signum:信号的编号。
返回值:
成功返回 0,失败返回 -1。
abort 函数
#include <stdlib.h> void abort(void);
功能:向进程发送一个 SIGABRT 信号,默认情况下进程会退出。
注意:即使 SIGABRT 信号被加入阻塞集,一旦进程调用了 abort 函数,进程也还是会被终止,且在终止前会刷新缓冲区,关文件描述符
pause 函数
#include <unistd.h>
int pause(void);
功能:将调用进程挂起直至捕捉到信号为止。这个函数通常用于判断信号是否已到。
返回值:直到捕获到信号,pause 函数才返回-1,且 errno 被设置成 EINTR。
进程接收到信号后的处理方式
1、执行系统默认动作
2、忽略此信号
3、执行自定义信号处理函数 程序中可用函数 signal()改变信号的处理方式。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
功能:注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。
参数:
signum:信号编号
handler 的取值:
忽略该信号:SIG_IGN
执行系统默认动作:SIG_DFL
自定义信号处理函数:信号处理函数名
返回值:
成功:返回函数地址,该地址为此信号上一次注册的信号处理函数的地址。 失败:返回 SIG_ERR
信号代码演示
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void my_signal(int arg)
{
printf("接收到信号\n");
}
int main()
{
signal(14,my_signal);
alarm(5);
//这里有个奇怪的现象,设定闹钟后,sleep后面如果有其他信号将失效
sleep(100);
printf("hello\n");
//捕捉信号
// printf("挂起中\n");
// int id =pause();
// if(id == -1)
// {
// printf("普抓到信号\n");
// }
// kill(getpid(),2);
while(1);
}
可重入函数
可重入函数是指函数可以由多个任务并发使用,而不必担心数据错误。
编写可重入函数:
1、不使用(返回)静态的数据、全局变量(除非用信号量互斥)。
2、不调用动态内存分配、释放的函数。
3、不调用任何不可重入的函数(如标准 I/O 函数)。
注:即使信号处理函数使用的都是可重入函数(常见的可重入函数),也要注意进入处理函数时,首先要保存 errno 的值,结束时,再恢复原值。因为,信号处理过程中,errno 值随时可能被改变。
常见的可重入函数列表:
信号集
信号集概述
一个用户进程常常需要对多个信号做出处理。为了方便对多个信号进行处理,在 Linux 系统中引入了信号集。 信号集是用来表示多个信号的数据类型。
信号集数据类型
sigset_t
定义路径:
/usr/include/i386-linux-gnu/bits/sigset.h
信号集相关的操作主要有如下几个函数: sigemptyset sigfillset sigismember sigaddset sigdelset
sigemptyset 函数
初始化一个空的信号集
#include <signal.h>
int sigemptyset(sigset_t *set);
功能:初始化由 set 指向的信号集,清除其中所有的信号即初始化一个空信号集。
参数:
set:信号集标识的地址,以后操作此信号集,对 set 进行操作就可以了。
返回值: 成功返回 0,失败返回 -1。
sigfillset 函数
初始化一个满的信号集 #include <signal.h>
int sigfillset(sigset_t *set);
功能:初始化信号集合 set, 将信号集合设置为所有信号的集合。
参数:
信号集标识的地址,以后操作此信号集,对 set 进行操作就可以了。
返回值: 成功返回 0,失败返回 -1。
sigismember 函数
判断某个集合中是否有某个信号
#include <signal.h>
int sigismember(const sigset_t *set,int signum);
功能:查询 signum 标识的信号是否在信号集合 set 之中。
参数:
set:信号集标识符号的地址。
signum:信号的编号。
返回值: 在信号集中返回 1,不在信号集中返回 0 错误,返回 -1
sigaddset 函数
#include <signal.h>
int sigaddset(sigset_t *set, int signum);
功能:将信号 signum 加入到信号集合 set 之中。
参数:
set:信号集标识的地址。
signum:信号的编号。
返回值: 成功返回 0,失败返回 -1。
sigdelset 函数
从某个信号集中删除一个信号 #include <signal.h>
int sigdelset(sigset_t *set, int signum);
功能:将 signum 所标识的信号从信号集合 set 中删除。
参数:
set:信号集标识的地址。
signum:信号的编号。
返回值: 成功:返回 0 失败:返回 -1
信号集代码演示
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main(){
//初始化空信号集
sigset_t set;
// sigemptyset(&set);
//初始化满信号集
//sigfillset(&set);
//某个信号是否在集合
//添加某个信号到集合中
sigaddset(&set,2);
sigaddset(&set,3);
//移除集合里的某个信号
sigdelset(&set, 2);
int fd = sigismember(&set, 3);
if (fd == 1)
{
printf("有这个信号\n");
}
else
{
printf("不在集合中\n");
}
}
信号阻塞集(屏蔽集、掩码)
每个进程都有一个阻塞集,它用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程 准备好时再将信号通知进程)。 所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除,且对应的信号 在被阻塞时发生了,进程将会收到相应的信号。
sigprocmask 函数
创建一个阻塞集合
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而 原先的信号阻塞集合由 oldset 保存。
参数:
how:信号阻塞集合的修改方法。
set:要操作的信号集地址。
oldset:保存原先信号集地址。
how:
SIG_BLOCK:向信号阻塞集合中添加 set 信号集
SIG_UNBLOCK:从信号阻塞集合中删除 set 集合
SIG_SETMASK:将信号阻塞集合设为 set 集合
注:若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到 oldset 中。
返回值: 成功:返回 0 失败:返回 -1
阻塞集代码演示
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main()
{
// 初始化空信号集
sigset_t set;
sigemptyset(&set);
//初始化满信号集
//sigfillset(&set);
//某个信号是否在集合
//添加某个信号到集合中
sigaddset(&set,2);
sigaddset(&set,3);
//移除集合里的某个信号
// sigdelset(&set, 2);
//添加阻塞集合
int pro = sigprocmask(SIG_BLOCK, &set, NULL);
if(pro == 0)
{
printf("add sucess\n");
}
else
{
printf("error\n");
}
//把2号3号信号放入阻塞集,所以ctrl+c无效,五秒后移除之后才有效
sleep(5);
int pro1 = sigprocmask(SIG_UNBLOCK, &set, NULL);
if(pro1 == 0)
{
printf("delete sucess\n");
}
else
{
printf("error\n");
}
sleep(5);
}