Linux网络编程【信号】2
08. 信号集
8.1 信号集概述
在PCB中有两个非常重要的信号集。一个称之为“阻塞信号集”,另一个称之为“未决信号集”。
这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对其进行位操作。而需自定义另外一个集合,借助信号集操作函数来对PCB中的这两个信号集进行修改。
8.2 自定义信号集函数
为了方便对多个信号进行处理,一个用户进程常常需要对多个信号做出处理,在 Linux 系统中引入了信号集(信号的集合)。
这个信号集有点类似于我们的 QQ 群,一个个的信号相当于 QQ 群里的一个个好友。
信号集是一个能表示多个信号的数据类型,sigset_t set,set即一个信号集。既然是一个集合,就需要对集合进行添加/删除等操作。
相关函数说明如下:
#include <signal.h>
int sigemptyset(sigset_t *set); //将set集合置空
int sigfillset(sigset_t *set); //将所有信号加入set集合
int sigaddset(sigset_t *set, int signo); //将signo信号加入到set集合
int sigdelset(sigset_t *set, int signo); //从set集合中移除signo信号
int sigismember(const sigset_t *set, int signo); //判断信号是否存在
示例代码:
void show_set(sigset_t *set)
{
for(int i=1; i<32; i++)
{
// 判断 SIGINT 是否在信号集 set 里
// 在返回 1, 不在返回 0
if(sigismember(set,i))
{
printf("1");
}
else
{
printf("0");
}
}
putchar('\n');
}
//信号处理函数
int main()
{
sigset_t set;
sigemptyset(&set); //清空集合
show_set(&set);
sigfillset(&set);//将所有的集合加入到set集合中
show_set(&set);
sigdelset(&set,SIGINT);//将信号2,3从集合移除
sigdelset(&set,SIGQUIT);
show_set(&set);
sigaddset(&set,SIGQUIT);//将信号3加入到集合
show_set(&set);
return 0;
}
运行结果:
8.3 sigprocmask函数
信号阻塞集也称信号屏蔽集、信号掩码。每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)。
所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。
我们可以通过 sigprocmask() 修改当前的信号掩码来改变信号的阻塞情况。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:
检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。
参数:
how : 信号阻塞集合的修改方法,有 3 种情况:
SIG_BLOCK:向信号阻塞集合中添加 set 信号集,新的信号掩码是set和旧信号掩码的并集。相当于 mask = mask|set。
SIG_UNBLOCK:从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 set 中的信号。相当于 mask = mask & ~ set。
SIG_SETMASK:将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内容清空,然后按照 set 中的信号重新设置信号阻塞集。相当于mask = set。
set : 要操作的信号集地址。
若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到 oldset 中。
oldset : 保存原先信号阻塞集地址
返回值:
成功:0,
失败:-1,失败时错误代码只可能是 EINVAL,表示参数 how 不合法。
代码示例:
//信号处理函数1
void fun1(int signum)
{
printf("捕捉到信号:%d\n",signum);
}
//信号处理函数2
void fun2(int signum)
{
printf("捕捉到信号:%d\n",signum);
}
//信号屏蔽集函数
int main()
{
//捕捉信号
//Ctrl + C
signal(SIGINT ,fun1);
//Ctrl + \ a
signal(SIGQUIT, fun2);
int ret = -1;
sigset_t set;
sigset_t oldset;
printf("按下任意键 阻塞信号2\n");
getchar();
sigemptyset(&set);//清空
sigemptyset(&oldset);
sigaddset(&set,SIGINT);//设置信号2
//设置阻塞信号2
ret = sigprocmask(SIG_BLOCK,&set,&oldset);
if(ret == -1)
{
perror("sigprocmask");
return 1;
}
printf("设置屏蔽编号为2的信号成功...\n");
printf("按任意键解除编号为2的信号阻塞...\n");
getchar();
//将信号屏蔽设置为原来的集合
ret = sigprocmask(SIG_SETMASK,&oldset,NULL);
if(ret == -1)
{
perror("sigprocmask");
return 1;
}
printf("按任意键退出...\n");
getchar();
return 0;
}
运行结果:
xcc@ubuntu:~/7th$ ./a.out
按下任意键 阻塞信号2
^\捕捉到信号:3
^C捕捉到信号:2
^C捕捉到信号:2设置屏蔽编号为2的信号成功…
按任意键解除编号为2的信号阻塞…
^C ^C ^C ^\捕捉到信号:3
^C ^C ^C ^C ^C ^C ^C
捕捉到信号:2
按任意键退出…
^C捕捉到信号:2
^C捕捉到信号:2
^\捕捉到信号:3xcc@ubuntu:~/7th$
结论:在解除阻塞前键入多个^C也只会捕捉到键入的最后一个信号,只会捕捉一次。信号不支持排队,解除之前其余信号都会丢失。
8.4 sigpending函数
#include <signal.h>
int sigpending(sigset_t *set);
功能:读取当前进程的未决信号集
参数:
set:未决信号集
返回值:
成功:0
失败:-1
示例程序:
09. 信号捕捉
9.1 信号处理方式
一个进程收到一个信号的时候,可以用如下方法进行处理:
1)执行系统默认动作
对大多数信号来说,系统默认动作是用来终止该进程。
2)忽略此信号(丢弃)
接收到此信号后没有任何动作。
3)执行自定义信号处理函数(捕获)
用用户定义的信号处理函数处理该信号。
【注意】:SIGKILL 和 SIGSTOP 不能更改信号的处理方式,因为它们向用户提供了一种使进程终止的可靠方法。(下图两种)
内核实现信号捕捉过程:
9.2 signal函数
#include <signal.h>
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:
注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞。
参数:
signum:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill - l("l" 为字母)进行相应查看。
handler : 取值有 3 种情况:
SIG_IGN:忽略该信号
SIG_DFL:执行系统默认动作
信号处理函数名:自定义信号处理函数,如:func
回调函数的定义如下:
void func(int signo)
{
// signo 为触发的信号,为 signal() 第一个参数的值
}
返回值:
成功:第一次返回 NULL,下一次返回此信号上一次注册的信号处理函数的地址。如果需要使用此返回值,必须在前面先声明此函数指针的类型。
失败:返回 SIG_ERR
该函数由ANSI定义,由于历史原因在不同版本的Unix和不同版本的Linux中可能有不同的行为。因此应该尽量避免使用它,取而代之使用sigaction函数。
示例程序:
//信号处理函数1
void fun1(int signum)
{
printf("捕捉到信号:%d\n",signum);
}
//信号处理函数2
void fun2(int signum)
{
printf("捕捉到信号:%d\n",signum);
}
//信号注册函数
int main()
{
//Ctrl + C
signal(SIGINT ,fun1);
//Ctrl + \ a
signal(SIGQUIT, fun2);
while(1);//不让程序结束
return 0;
}
运行结果:
xcc@ubuntu:~/7th$ ./a.out
^C捕捉到信号:2
^\捕捉到信号:3
^\捕捉到信号:3
^\捕捉到信号:3
^C捕捉到信号:2
^C捕捉到信号:2
^Z
[2]+ 已停止 ./a.out
9.3 sigaction函数
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
功能:
检查或修改指定信号的设置(或同时执行这两种操作)。
参数:
signum:要操作的信号。
act: 要设置的对信号的新处理方式(传入参数)。
oldact:原来对信号的处理方式(传出参数)。
如果 act 指针非空,则要改变指定信号的处理方式(设置),如果 oldact 指针非空,则系统将此前指定信号的处理方式存入 oldact。
返回值:
成功:0
失败:-1
struct 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); //已弃用
};
\1) sa_handler、sa_sigaction:信号处理函数指针,和 signal() 里的函数指针用法一样,应根据情况给sa_sigaction、sa_handler 两者之一赋值,其取值如下:
a) SIG_IGN:忽略该信号
b) SIG_DFL:执行系统默认动作
c) 处理函数名:自定义信号处理函数
\2) sa_mask:信号阻塞集,在信号处理函数执行过程中,临时屏蔽指定的信号。
\3) sa_flags:用于指定信号处理的行为,通常设置为0,表使用默认属性。它可以是一下值的“按位或”组合:
Ø SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃)
Ø SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
Ø SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
Ø SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
Ø SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
Ø SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。
- 点赞
- 收藏
- 关注作者
评论(0)