文章目录
信号是什么
信号是一种用于在软件层面通知进程发生了某个事件的机制。这些事件可以是来自其他进程的通知、异常情况、用户的交互等。当系统检测到某个事件时,会向相应的进程发送一个信号。进程可以事先定义信号的处理方式,比如忽略、捕获、或执行默认操作。
因此综合起来,将信号称为软中断是因为它类似于硬中断的概念,但它是在软件层面实现的。信号提供了一种轻量级的通信和事件处理机制,允许进程在运行时响应各种事件,而不需要像硬中断那样涉及到硬件层面的处理。
相关函数
kill
int kill(pid_t pid,int sig);
作用:向指定进程发送信号
raise
int raise(int sig);
作用:向自身进程发送信号
signal
typedef void (*sighandler_t)(int);//中断函数类型
sighandler_t signal(int signum,sighandler_t handler);
作用:
为指定信号指定一个处理函数
sigaction
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
作用:
为指定信号注册中断函数,通过struct sigaction 结构体赋值,通过保存oldact保存旧的中断服务函数。
该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。
sigaction和signal
ignal比sigaction简单,但signal注册的信号在sa_handler被调用之前把会把信号的sa_handler指针恢复,而sigaction注册的信号在处理信号时不会恢复sa_handler指针。所以用signal函数注册的信号处理函数只会被调用一次,之后收到这个信号将按默认方式处理,如果想一直处理这个信号的话就得在信号处理函数中再次用signal注册一次。sigaction注册的函数多次调用不会恢复初始值。这两个函数可以为除SIGKILL和SIGSTOP外的任何一个特定有效的信号。
sigqueue
int sigqueue(pid_t pid, int signo, const union sigval value);
函数作用:新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction配合使用。
第一个参数: 指定接收信号的进程id
第二个参数:确定即将发送的信号
第三个参数:是一个联合结构体union sigval,指定了信号传递的参数,即通常所说的4字节值
alarm
unsigned int alarm(unsigned int section);
作用:
定时,指定时间之后向进程发送SIGALRM信号
指定时间为0,取消前一个定时。
只能使用一次,下一次使用需要再次设置
ualarm
设置定时器,相比于alarm,ualarm可以循环使用。
pause
int pause(void);
挂起进程,直到接收到一个信号好,并处理完信号后才返回。
strsignal 和 psignal
将信号量转化为字符描述。
信号集
阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。内核为每个进程都维护了一个信号掩码(信号集)
信号在内核中的表示:递达(Delivery)、未决(Pending)、阻塞(Block)
递达(Delivery):执行信号的动作
未决(Pending):被阻塞的信号处在的状态,信号从产生到递达之间的状态
阻塞(Block):可以理解为屏蔽信号,一个信号可以若被阻塞,它将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看作是这样的:
图中:
block集(阻塞集、屏蔽集):一个进程所要屏蔽的信号,在对应要屏蔽的信号位置1
pending集(未决信号集):如果某个信号在进程的阻塞集之中,则也在未决集中对应位置1,表示该信号不能被递达,不会被处理
handler(信号处理函数集):表示每个信号所对应的信号处理函数,当信号不在未决集中时,将被调用
每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,
SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
原文链接:https://blog.youkuaiyun.com/z5z5z5z56/article/details/107597308
#include <signal.h>
int sigemptyset(sigset_t *set);/* Clear all signals from SET. 清空信号集*/
int sigfillset(sigset_t *set);// 把所有信号的对应bit置位=把64种信号都放入信号集(没有32 33) /* Set all signals in SET. */
int sigaddset(sigset_t *set, int signo);// 增加某个指定信号
int sigdelset(sigset_t *set, int signo);//删除某个指定信号
int sigismember(const sigset_t *set, int signo);// 查看信号是否在集合里面 /* Return 1 if SIGNO is in SET, 0 if not. */
注意,在使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。上面四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。
sigprocmask
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
如果oldset是非空指针,则读取进程的当前信号屏蔽字通过oldset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oldset和set都是非空指针,则先将原来的信号屏蔽字备份到oldset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
sigpending
int sigpending(sigset_t *set);
查看所有未决信号集的值==>那些信号被阻塞。
sigsuspend
int sigsuspend(const sigset_t * mask);
作用:使用mask值替换信号掩码,并挂起进程(原子操作);
实时信号的处理
信号编号小于等于31的是非实时信号,这种信号不可靠,当多个同类信号到达时可能会发生信号的覆盖。
信号编号大于等于34的是实时信号,这种信号可靠,当多个同类信号到达时不会发生信号的覆盖,使用队列存储。
在处理传统信号时,操作系统默认情况下只保留一个挂起的信号。如果在处理信号时,相同类型的信号再次到达进程,且之前的信号尚未处理完毕,则后续到达的信号会被丢弃。
实时信号使用了队列的机制,即使在处理实时信号时,多个相同类型的信号会被排队等待处理。
实时信号有一个优先级的概念,具有更高优先级的实时信号会在低优先级实时信号之前得到处理。
sigaction和sigqueue处理实时函数
sigqueue在发送信号时可以通过一个联合体携带一些数据,这个数据可以被sigaction指定的中断函数的第二个参数接收,但使用的前提是sigaction绑定时要通过结构体参数struct sigaction指定SA_SIGINFO。
sa_handler的原型是一个参数为int,返回类型为void的函数指针。参数即为信号值,所以信号不能传递除信号值之外的任何信息;
sa_sigaction的原型是一个带三个参数,类型分别为int,struct siginfo *,void *,返回类型为void的函数指针。第一个参数为信号值;第二个参数是一个指向struct siginfo结构的指针,此结构中包含信号携带的数据值;第三个参数没有使用。
sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。默认当前信号本身被阻塞。
sa_flags包含了许多标志位,比较重要的一个是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以传递到信号处理函数中。即使sa_sigaction指定信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误。
sa_restorer已过时,POSIX不支持它,不应再使用。
因此,当你的信号需要接收附加信息的时候,你必须给sa_sigaction赋信号处理函数指针,同时还要给sa_flags赋SA_SIGINFO,类似下面的代码:
#include <signal.h>
……
void sig_handler_with_arg(int sig,siginfo_t *sig_info,void *unused){……}
int main(int argc,char **argv)
{
struct sigaction sig_act;
……
sigemptyset(&sig_act.sa_mask);
sig_act.sa_sigaction=sig_handler_with_arg;
sig_act.sa_flags=SA_SIGINFO;
……
}
如果你的应用程序只需要接收信号,而不需要接收额外信息,那你需要的设置的是sa_handler,而不是sa_sigaction,你的程序可能类似下面的代码:
#include <signal.h>
……
void sig_handler(int sig){……}
int main(int argc,char **argv)
{
struct sigaction sig_act;
……
sigemptyset(&sig_act.sa_mask);
sig_act.sa_handler=sig_handler;
sig_act.sa_flags=0;
……
}
为什么没有信号32和33
posix标准未定义这两个信号
kill 和 sigqueue
1、kill 和 sigqueue 指定的sig为0可以判断pid指向的进程是否存在。
2、kill发送信号,但是不能带参数
3、aigqueue发送信号可以通过联合体带参数到中断处理函数。
pause sigsuspend sleep的区别
1)pause()会令目前的进程暂停(进入睡眠状态),直至信号(signal)出现,处理完中断后返回。并且返回值是导致它返回的那个信号的编号。
2)sigsuspend函数和pause函数一样,可以是进程挂起(进入睡眠状态),直至有信号发生。但是sigsuspend函数的参数是一个信号集,这个信号集是用来屏蔽信号的,信号集中存放了要屏蔽的信号。被屏蔽的信号无法唤醒进程。如果该信号集为空的话,sigsuspend就不屏蔽任何信号,任何信号都可以使进程从挂起状态唤醒,这就与pause函数一样了。
3)sleep让进程暂停指定时间,指定时间后自动唤醒进程。时间以秒为单位。不需要外部信号。在POSIX系统上,sleep() 函数返回0。但是,如果调用被信号中断,它可能返回剩余需要睡眠的时间(尽管这种行为依赖于具体的实现)。