信号集是一个或多个信号的集合,主要用在信号屏蔽函数中。数据类型为sigset_t。
信号屏蔽字是指一个进程中当前阻塞而不能送给该进程的信号集。
与信号集设置相关的函数有下面几个:
#include <signal.h>
int sigemptyset(sigset_t *set);
功能:set为信号集,将信号集清空,对应将所有信号屏蔽字置0;
int sigfillset(sigset_t *set);
功能:将所有信号加入到信号集中,对应将所有信号屏蔽字置1;
int sigaddset(sigset_t *set, int signo);
功能:将某个信号加入到信号集中,对应将信号屏蔽字某位置1;
int delset(sigset *set, int signo);
功能:将某个信号从信号集中删除,对应将信号屏蔽字某位置0;
上述函数返回:成功返回0,出错返回-1;
int sigismember(const sigset_t *set, int signo);
功能:测试信号集中是否包含某个信号,对应判断信号屏蔽字某位是否为1;
返回值:真返回1,假返回0,出错返回-1;
注意:所有进程在使用信号集前,要对信号集调用sigemptyset或sigfillset一次。因为C编译器把未赋初值的外部和静态变量都初始化为0,而这与是否给定的信号集实现对应却并不清楚。
设置信号屏蔽字要调用下面的信号屏蔽函数。
#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
功能:利用set去覆盖内核中信号屏蔽字,oset存放原有的信号屏蔽字;
返回:成功返回0,出错返回-1;
int sigpending(sigset_t *set);
功能:获取信号未决字的内容(通过set返回当前正在阻塞的信号的信号集);
返回值:成功返回0,出错返回-1;
参数how
- SIG_BLOCK:利用set中信号设置信号屏蔽字;
- SIG_UNBLOCK:利用set中信号不设置信号屏蔽字;
- SIG_SETMASK:利用set中信号去替换内核信号屏蔽字;
说明:1 进程可以暂时屏蔽信号,使得进程在执行过程中发生的相应信号暂时被阻塞,等待进程解除信号屏蔽后再由内核或驱动将该 信号递送给进程;
2 信号屏蔽可屏蔽程序执行过程中的中断;
3 若oset是非空指针,则返回进程的当前信号屏蔽字;
4 若set为空,则进程信号屏蔽字不变,how值无意义;
5 注意,不可以阻塞SIGKILL和SIGSTOP信号;
下面给出一个mask(信号屏蔽字)的实例。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void out_set(sigset_t set)
{
int i = 1;
for(; i < 31; ++i)
{
// 判断信号屏蔽字的某些位是否置1
if(sigismember(&set, i))
{
printf("%d\n", i);
}
}
}
void sig_handler(int signo)
{
printf("begin process the %d\n", signo);
// 获得正在处理信号时内核中的信号屏蔽字内容
sigset_t oset;
sigemptyset(&oset);
if(sigprocmask(SIG_BLOCK, NULL, &oset) < 0)
perror("sigprocemask error");
out_set(oset);// 输出处理信号时的信号屏蔽字
printf("finish process the %d\n", signo);
}
int main(void)
{
if(signal(SIGUSR1, sig_handler) == SIG_ERR)
perror("signal sigusr1 error");
if(signal(SIGUSR2, sig_handler) == SIG_ERR)
perror("signal sigusr2 error");
if(signal(SIGINT, sig_handler) == SIG_ERR)
perror("signal sigint error");
// 定义一个信号集
sigset_t oset;//放置内核信号屏蔽字的内容
printf("before signal occured mask:\n");
// 清空信号集oset
sigemptyset(&oset);
//在信号发生前,获得信号屏蔽字的内容
if(sigprocmask(SIG_BLOCK, NULL, &oset) < 0)
perror("sigprocmask error");
out_set(oset);//输出产生信号前的信号屏蔽字
printf("process %d wait signal...\n", getpid());
pause();// 进程暂停等待信号
printf("after signal occured mask:\n");
sigemptyset(&oset);
// 信号发生后(信号处理完毕后),获得信号屏蔽字的内容
if(sigprocmask(SIG_BLOCK, NULL, &oset) < 0)
perror("sigprocmask error");
out_set(oset);// 输出信号产生并处理完毕后的信号屏蔽字的内容
return 0;
}
程序分别输出信号产生前,处理信号时和信号处理完毕三个阶段的信号屏蔽字。
由结果可以看出,在信号产生前后,信号屏蔽字全置为了0。
下面给出一个pending(信号未决字)的实例。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void out_set(sigset_t set)
{
int i = 1;
for(; i <= 31; ++i)
{
if(sigismember(&set, i))
{
printf("%d,", i);
}
}
printf("\n");
}
void sig_handler(int signo)
{
printf("begin the signal handler\n");
int i = 0;
sigset_t set;
//循环不断去获得同类信号
for(; i < 20; ++i)
{
sigemptyset(&set);
if(sigpending(&set) < 0)
perror("sigpending error");
else
{
printf("pending signal:");
out_set(set);
sigemptyset(&set);
}
printf("i is %d\n", i);
sleep(1);
}
printf("end the signal handler\n");
}
int main(void)
{
if(signal(SIGTSTP, sig_handler) == SIG_ERR)
perror("signal sigtstp error");
printf("process %d wait signal...\n", getpid());
pause(); // 进程暂停等待信号
printf("process finished\n");
return 0;
}
在for循环中,我们不断的去获得SIGTSTP信号,当同时有两个及以上的SIGTSTP信号产生时,相应的信号未决字才被置为1。同时,如果产生多个SIGTSTP信号(2个以上),则只被处理两次,其他的被屏蔽掉。
利用信号屏蔽技术可以防止函数执行过程中被信号中断,下面给出一个实例来说明。
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
int g_v[10];//全局变量
int *h_v;//堆中的数据
void set(int val)
{
int a_v[10];// 局部变量
int i = 0;//注意到这里的i是局部变量
for(; i < 10; ++i)
{
a_v[i] = val;
g_v[i] = val;
h_v[i] = val;
sleep(1);//给一个发送信号的时间
}
printf("g_v:");
for(i = 0; i < 10; ++i)
{
if(i != 0)
printf(",%d", g_v[i]);
else
printf("%d", g_v[i]);
}
printf("\n");
printf("h_v:");
for(i = 0; i < 10; ++i)
{
if(i != 0)
printf(",%d", h_v[i]);
else
printf("%d", h_v[i]);
}
printf("\n");
printf("a_v:");
for(i = 0; i < 10; ++i)
{
if(i != 0)
printf(",%d", a_v[i]);
else
printf("%d", a_v[i]);
}
printf("\n");
}
void sig_handler(int signo)
{
if(signo == SIGTSTP)
{
printf("SIGTSTP occured\n");
set(20);// 在信号处理函数中又调用set
printf("end SIGTSTP\n");
}
}
int main(void)
{
if(signal(SIGTSTP, sig_handler) == SIG_ERR)
perror("signal sigtstp error");
h_v = (int*)calloc(10, sizeof(int));
printf("begin running main\n");
//屏蔽信号(1~31)
sigset_t sigset;
sigemptyset(&sigset);
sigfillset(&sigset);// 要屏蔽所有的信号
//这里我们不需要获得原内核中的信号屏蔽字,第三个参数设为NULL
if(sigprocmask(SIG_SETMASK, &sigset, NULL) < 0)
perror("sigprocmask error");
set(10);
//解除信号屏蔽
if(sigprocmask(SIG_UNBLOCK, &sigset, NULL) < 0)
perror("sigprocmask error");
printf("end running main\n");
return 0;
}
这里我们对“函数的可重入性”中的函数进行了改进。在执行set函数之前,先通过sigfillset函数和sigprocmask函数屏蔽了1~31号信号,这样的话,在执行set过程中如果发生了信号,则不会影响第一次的结果输出。后面我们对信号解除了屏蔽,这样的话,在第一次执行set中产生的信号仍然被捕获,再次执行set函数。运行结果如下:
在第一次赋值过程中产生了一个SIGTSTP信号。