- 信号的概念:信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断。
软中断信号用来通知进程发生了异步事件,进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。注意,信号只是用来通知某进程发生了什么事件,并不给进程传递任何数据。
收到信号的进程对各种信号有不同的处理方法。具体方法在下文会有详细的介绍。进程通过系统调用signal来指定进程对某个信号的处理行为。 - 产生信号有以下4种方式:
1.用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,例如:crtl+c产生SIGINT信号,ctrl+\产生SIGQUIT信号,crtl+z产生SIGTSTP信号(可使前台进程停止)(一个命令后面加&可以放到后台运行)
2.硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如:当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程
3.一个进程调用kill(2)函数可以发送信号给另一个进程。可以用kill(1)命令发送信号给某个进程,kill(1)命令也是调用kill(2)函数实现的,如果不明确指定信号则发送SIGTERM信号,该信号的默认处理动作是终止进程,当该内核检测到某种软件条件发生时也可以通过信号通知进程。例如:闹钟超时产生SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。如果不想按默认动作处理信号,用户程序可以调用sigaction(2)函数告诉内核如何处理某种信号。
4.软件条件产生。 - 处理信号的常见方式有以下3种(忽略、默认、自定义):
1.忽略此信号
2.执行该信号的默认处理动作(部分进程默认状态一般是终止进程)
3.提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉一个信号 - 信号其他相关常见概念:
实际执行信号的处理动作称为信号递达(Delivery)
信号从产生到递达之间的状态称为信号未决(Pending)
进程可以选择阻塞(Block)某个信号
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作
注:
1.如果一个信号被Block一定不会被递达
2.如果一个信号没有被Block,但被Pending但不会立即递达,而是在合适的时候
信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。
SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。 - 信号的几个缺点:
1.系统开销太大
2.发送信号的进程需要进行系统调用
3.数量非常有限
4.不能传送数据及参数
首先介绍一个数据类型:
sigset_t
是位图类型,用户想要访问block表和pending表需要通过系统调用接口
信号集操作函数
头文件:#include<signal.h>
int sigemptyset(sigset_t *set);设置为空
int sigfillset(sigset_t *set);填充
int sigaddset(sigset_t *set,int signo);添加
int sigdelset(sigset_t *set,int signo);删除
int sigismember(const sigset_t *set,int signo);判断是否在信号集中
前四个函数都是成功返回0,出错返回-1。
sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。
- 可以调用sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)
阻塞信号集也叫作当前进程的信号屏蔽字,这里的屏蔽是阻塞而不是忽略
头文件:#include<signal.h>
函数原型:int sigpromask(int how,const sigset_t *set,sigset_t *oset);
返回值:若成功返回0;若出错则为1
参数:sigset_t -> 信号集
第三个参数为输出型参数,目的是为了保存老的信号屏蔽字,若不想保存设置为NULL,就可以
SIG_BLOCK
set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask = mask|set
SIG_UNBLOCK
set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask = mask&set
SIG_SETMASK
设置当前信号屏蔽字为set所指向的值,相当于mask = set
如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
- 可以调用sigpending函数来获取pengding信号集
头文件:#include<signal.h>
函数原型:int sigpending(sigset_t *set);
返回值:若成功返回0;若出错则为1
功能:读取当前进程的未决信号集,通过set参数传出
- 下面是用刚学的几个函数做的实验:
#include <stdio.h>
#include<signal.h>
#include<unistd.h>
void printsigset(sigset_t *set)
{
int i = 0;
for(;i<32;i++)
{
if(sigismember(set,i))//判断指定信号是否在目标集和中
{
putchar('1');
}
else
{
putchar('0');
}
}
puts("");
}
int main()
{
sigset_t s,p;//定义信号集对象
sigemptyset(&s);//清空,初始化
sigaddset(&s,SIGINT);//相当于ctrl+c
sigprocmask(SIG_BLOCK,&s,NULL);//设置阻塞信号集,阻塞SIGINT信号
while(1)
{
sigpending(&p);//获取未决信号集
printsigset(&p);
sleep(1);
}
return 0;
}
运行结果:
程序运行时,每秒钟把各个信号的未决状态打印一遍,由于我们阻塞了SIGINT信号,按ctrl+c将会使SIGINT信号处于未决状态,但按ctrl+*仍然可以终止程序,因为*SIGQUIT信号没有阻塞。