一. 信号集操作函数
内核通过读取未决信号集来判断信号是否应被处理。信号屏蔽字mask可以影响未决信号集。而我们可以在应用程序中自定义set来改变mask。已达到屏蔽指定信号的目的。
PCB中有阻塞信号集(可操作)和未决信号集,但是未决信号集不可直接进行操作,要通过操作阻塞信号集来影响未决信号集。

1.1 信号集设定相关函数

前四个函数都是成功返回0 失败返回-1且设置errno
最后一个,若信号在信号集中返回1 不在则返回0
sigset_t set;// typedef unsigned long sigset_t;
1.1.1 sigemptyset
将某个信号集清0
1.1.2 sigfillset
将某个信号集置1
1.1.3 sigaddset
将某个信号加入信号集
1.1.4 sigdelset
将某个信号清出信号集
1.1.5 sigismember
判断某个信号是否在信号集中
1.2 sigprocmask函数

作用:用于获取或者更改调用线程的信号屏蔽集(signal mask),使自己设置的set与PCB中的阻塞信
号集产生关系
参数:how:取值与说明如下
| SIG_BLOCK | set表示需要屏蔽的信号,mask = mask|set |
| SIG_UNBLOCK | set表示需要解除屏蔽的信号,mask = mask & ~set |
| SIG_STEMASK | set表示用于替代原始屏蔽及的新屏蔽集,mask = set |
set:传入参数,是一个位图,set中哪位置1,就表示当前进程屏蔽哪个信号。
oldset:传出参数,保存旧的信号屏蔽集。
返回值:成功返回0 失败返回-1且errno
1.3 sigpending函数

作用:读取当前的未决信号集
参数:传出参数,传出当前的未决信号集
返回值:成功0 失败-1且errno
查看时可以使用函数sigismember函数,在的话打印1 不在的话打印0
1.4 示例代码
注意:9号SIGKILL 和19号 SIGSTOP信号,不允许忽略和捕捉,只能执行默认动作。甚至不能将
其设置为阻塞。
void sys_error(const char *str)
{
perror(str);
exit(1);
}
void print_set(sigset_t *set)
{
int i = 0;
for(i = 1; i < 32; i++)
{
if(sigismember(set,i))//查看信号是否在信号集中
printf("%d ",1);
else
printf("%d ",0);
}
puts("");
}
int main()
{
sigset_t set, oldset, pset;
int ret;
sigemptyset(&set);
sigaddset(&set, SIGINT);//添加信号到信号集中
sigaddset(&set, SIGQUIT);
sigaddset(&set, SIGBUS);
sigaddset(&set, SIGKILL);//这个信号不可被阻塞,忽略,捕捉
sigaddset(&set, SIGSTOP);//同上
ret = sigprocmask(SIG_BLOCK, &set, &oldset);//设置阻塞状态
if(ret == -1)
sys_error("sigprocmask()");
while(1)//循环打印,循环查看
{
ret = sigpending(&pset);//查看未决信号集
if(ret == -1)
sys_error("sigpending()");
print_set(&pset);
sleep(1);
}
return 0;
}
结果:

二. 信号捕捉
2.1 signal函数

作用:注册一个信号捕捉函数
参数:signum:对应的信号
handler:对应的信号处理函数,int代表信号的编号
返回值:返回信号处理函数
注意:该函数由ANSI定义,由于历史原因在不同版本的Unix和不同版本的Linux中可能有不同的行
为。因此应该尽量避免使用它,取而代之使用sigaction函数
示例代码:
void sys_error(const char *str)
{
perror(str);
exit(1);
}
void fun(int signo)
{
printf("catch you: %d\\n",signo);
}
int main()
{
signal(SIGINT, fun);
while(1);//主函数不结束,死循环
return 0;
}
结果

2.2 sigaction函数

作用:注册信号捕捉函数
参数:signum:要捕捉的信号
act:新的信号处理状态
oldact:保存旧有的信号处理状态
返回值:成功0 失败-1且errno
struct sigaction结构体:
struct sigaction {
void (*sa_handler)(int);/信号处理函数,int代表信号的编号
void (*sa_sigaction)(int, siginfo_t *, void *);/一般不使用
sigset_t sa_mask;/此信号屏蔽字只工作于信号捕捉函数执行期间
int sa_flags;/通常为0,默认属性
void (*sa_restorer)(void);/一般不使用
};
① sa_handler:指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略 或 SIG_DFL表执行默认动作
② sa_mask: 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。
③ sa_flags:通常设置为0,表使用默认属—本信号默认屏蔽。
示例代码:一个捕捉函数可以捕捉多个信号
void sys_error(const char *str)
{
perror(str);
exit(1);
}
void fun(int signo)
{
if(signo == SIGINT)//判断是哪个信号被捕捉到
printf("catch you: %d\\n",signo);
else if(signo == SIGQUIT)//
printf("—catch you: %d\\n",signo);
}
int main()
{
struct sigaction act, oldact;
act.sa_handler = fun;//设置回调函数
sigemptyset(&act.sa_mask);//晴空sa_mask屏蔽字,只在回调函数工作期间有效
act.sa_flags = 0;//默认值,回调函数执行期间,被捕捉信号自动屏蔽
//只会屏蔽当前被捕捉的一个信号,多个信号共用一个sa_handler
//时,不会互相屏蔽,要想屏蔽,必须手动设置sa_mask
int ret = sigaction(SIGINT, &act, &oldact);//注册捕捉信号
if(ret == -1)
sys_error("sigaction()");
ret = sigaction(SIGQUIT, &act, &oldact);//可以注册捕捉多个信号
if(ret == -1)
sys_error("sigaction()");
while(1);//死循环
return 0;
}
2.3 信号捕捉特性
1、进程正常运行时,默认PCB中有一个信号屏蔽字,假定为☆,它决定了进程自动屏蔽哪些信
号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很
长时间,在这期间所屏蔽的信号不由☆来指定。而是用sa_mask来指定。调用完信号处理函
数,再恢复为☆。
2、XXX信号捕捉函数执行期间,XXX信号自动被屏蔽,需要sa_flags设置为0。
3、阻塞的常规信号不支持排队,产生多次只记录一次。(后32个实时信号支持排队)
2.3.1 不支持排队示例代码
void sys_error(const char *str)
{
perror(str);
exit(1);
}
void fun(int signo)
{
printf("catch :%d\\n",signo);
sleep(5);//当sleep被信号打断后,再次回来时,会从sleep的下一条语句执行
//不会继续睡眠剩余的时间
printf("—-end :%d\\n",signo);
}
int main()
{
struct sigaction act, oldact;
act.sa_handler = fun;//设置回调函数
sigemptyset(&act.sa_mask);//晴空sa_mask屏蔽字,只在回调函数工作期间有效
act.sa_flags = 0;//默认值,回调函数执行期间,被捕捉信号自动屏蔽
//只会屏蔽当前被捕捉的一个信号,多个信号共用一个sa_handler
//时,不会互相屏蔽,要想屏蔽,必须手动设置sa_mask
int ret = sigaction(SIGINT, &act, &oldact);//注册捕捉信号
if(ret == -1)
sys_error("sigaction()");
ret = sigaction(SIGQUIT, &act, &oldact);//可以注册捕捉多个信号
if(ret == -1)
sys_error("sigaction()");
while(1);//死循环
return 0;
}
2.3.2 示例代码2
void sys_error(const char *str)
{
perror(str);
exit(1);
}
void fun1(int signo)
{
printf("catch :%d\\n",signo);
sleep(3);
}
void fun2(int signo)
{
printf("catch :%d\\n",signo);
sleep(3);
}
int main()
{
struct sigaction act1, act2, oldact1, oldact2;
act1.sa_handler = fun1;//设置回调函数
sigemptyset(&act1.sa_mask);//清空sa_mask屏蔽字,只在回调函数工作期间有效
act1.sa_flags = 0;//默认值,回调函数执行期间,被捕捉信号自动屏蔽
//只会屏蔽当前被捕捉的一个信号,多个信号共用一个sa_handler
//时,不会互相屏蔽,要想屏蔽,必须手动设置sa_mask
act2.sa_handler = fun2;//设置回调函数
sigemptyset(&act2.sa_mask);//晴空sa_mask屏蔽字,只在回调函数工作期间有效
act2.sa_flags = 0;//默认值,回调函数执行期间,被捕捉信号自动屏蔽
int ret = sigaction(SIGINT, &act1, &oldact1);//可以注册捕捉多个信号
if(ret == -1)
sys_error("sigaction()");
ret = sigaction(SIGQUIT, &act2, &oldact2);//可以注册捕捉多个信号
if(ret == -1)
sys_error("sigaction()");
while(1);//死循环
return 0;
}
2.4 内核实现信号捕捉过程

1、主控流程进入内核才会检查是否有信号产生
2、为什么信号处理函数执行完之后还要再次回到内核中,而不是直接返回到主控制流程?因为信
号处理函数是由内核调用的,函数必须返回至调用者处,所以返回至了内核中
三. 借助信号捕捉回收子进程
3.1 SIGCHLD(17)信号产生的条件
1、子进程终止时
2、子进程接收到SIGSTOP信号停止时
3、子进程处在停止态,接受到SIGCONT后唤醒时
3.2 借助SIGCHLD信号回收子进程
子进程结束运行,其父进程会收到SIGCHLD信号。该信号的默认处理动作是忽略。可以捕捉该信号,在捕捉函数中完成子进程状态的回收。
3.3 示例代码
这里有几点需要注意的:
1、在回调函数中,要循环的对子进程进程回收,防止一次有多个子进程结束,而父进程只回
收了其中一个。
2、另外在父进程注册信号捕捉函数之前,一定要对SIGCHLD这个信号设置屏蔽,避免在父
进程还未fork完就有子进程死亡。—应该在fork之前,阻塞SIGCHLD信号。注册完捕捉函
数后解除阻塞
3、子进程继承父进程的信号屏蔽字和信号处理动作,但子进程没有继承未决信号集spending
#define FORK_SIZE 10
void child_catch(int signum)
{
pid_t wpid;
int wstatus;
while(0 < (wpid = waitpid(-1, &wstatus, WNOHANG))){//使用非阻塞回收
if(WIFEXITED(wstatus)){
printf("——wait pid :%d,returnval:%d\\n",
wpid,WEXITSTATUS(wstatus));
}
else if(WIFSIGNALED(wstatus)){
printf("——wait pid :%d,returnval:%d\\n",
wpid,WTERMSIG(wstatus));
}
}
return ;
}
int main()
{
pid_t pid;
//设置对17号信号屏蔽,防止父进程还没有fork完就有子进程结束
sigset_t set, oldset;
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK, &set, &oldset);
int i = 0;
for(i = 0; i < FORK_SIZE; i++){
pid = fork();
if(pid == 0){
break;
}
else if(pid < 0){
perror("fork()");
exit(1);
}
}
if(i == FORK_SIZE){
printf("parent,pid :%d\\n",getpid());
//注册信号捕捉
struct sigaction act;
act.sa_handler = child_catch;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD, &act, NULL);
//完了之后解除屏蔽,解除屏蔽
sigprocmask(SIG_SETMASK, &oldset, NULL);
while(1);
}
else{
printf("[%d]:child,pid:%d\\n",i,getpid());
return i;
}
return 0;
}



