目录
一、信号的概念
二、信号的产生方式
通过键盘发送信号
通过系统调用,指令
异常
软件条件
三、信号怎么保存(原理)
信号其他相关常见概念
在内核中表示
sigset_t
四、信号的相关接口、指令,函数
signal
sigprocmask
sigpending
sigemptyset
sigfillset
sigaddset
sigdelset
sigismember
sigaction
五、信号怎么处理
信号什么时候被处理?
信号的捕捉图
一、信号的概念
信号是进程之间事件异步通知的一种方式,属于软中断。
在linux中通过指令:kill -l 可以查看信号列表
二、信号的产生方式
通过键盘发送信号
如常用的ctrl + c就是向前台进程发送2号信号(SIGINT)
通过系统调用,指令
如:
kill -2 1234,意思是向pid为1234的进程发送2号信号
或者
#include <sys/types.h>
#include <signal.h>int kill(pid_t pid, int sig);
作用:向指定pid的进程发送sig信号
返回值:如果成功(至少发送了一个信号),则返回0。如果出现错误,则返回-1,并适当地设置errno
异常
硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE(8号)信号发送给进程。再比如当前进程访问了非法内存地址,,MMU会产生异常,内核将这个异常解释为SIGSEGV(11号)信号发送给进程。
软件条件
例如:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟(闹钟也是一种软件条件),也就是告诉内核在seconds秒之后给当前进程发SIGALRM(14号)信号, 该信号的默认处理动作是终止当前进程。这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。
三、信号怎么保存(原理)
信号其他相关常见概念
实际执行信号的处理动作称为信号递达(Delivery)
信号从产生到递达之间的状态,称为信号未决(Pending)。
进程可以选择阻塞 (Block )某个信号。
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
在内核中表示
- 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
- SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
- SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。
sigset_t
从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。
四、信号的相关接口、指令,函数
signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:对信号进行自定义捕捉
参数:
signum:信号,如2号信号就填2或者SIGINT,注意:SIGKILL(9号)0和SIGSTOP(19号)不可忽略和自定义捕捉
handler:一个函数指针,函数内容为自定义的操作
返回值:成功返回自定义的函数指针,失败返回SIG_ERR。
使用例子:
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
using namespace std;
void handler(int signo)
{
while (1)
{
cout << "我是" << signo << "号信号,我被自定义捕捉了" << endl;
sleep(1);
}
}
int main()
{
cout << getpid() << endl;
signal(2, handler);
while (1)
{
cout << "我没有被自定义捕捉" << endl;
sleep(1);
}
return 0;
}
sigprocmask
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:读取或更改进程的信号屏蔽字(阻塞信号集,其存储结构为位图)参数:
how:读取或更改方式
有三种选择:
假设当前的信号屏蔽字为mask。
- SIG_BLOCK:set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set
- SIG_UNBLOCK:set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set
- SIG_SETMASK:设置当前信号屏蔽字为set所指向的值,相当于mask=set
set:如果set不为非空指针,则按照set更改进程的屏蔽字
oldset:如果set和oldset都不为非空指针,则把先前信号的屏蔽字备份给oldset,再更改进程的屏蔽字,如果不许要备份先前信号的屏蔽字,则此参数设为nullptr
sigpending
#include <signal.h>
int sigpending(sigset_t *set);
功能:读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。
sigemptyset
sigfillset
sigaddset
sigdelset
sigismember
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
功能:
sigemptyset()将set给出的信号集初始化为空,将所有信号从该集合中排除。
sigfillset()初始化set为full(比特位全为1),包括所有信号。
sigaddset()和sigdelset()分别从set中添加和删除信号符号。
sigismember()测试sigum是否是set的成员。
set:阻塞信号集
返回值:
sigemptyset()、sigfillset()、sigaddset()和sigdelset()成功时返回0,错误时返回-1。
sigismember()如果signum是set的成员返回1,如果signum不是set的成员返回0,如果出错返回-1。当出现错误时,这些函数设置errno来指示原因。
上面函数的综合使用例子
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
using namespace std;
void printsigset(sigset_t *s)
{
for (int i = 31; i > 0; i--)
{
if (sigismember(s, i))
{
cout << "1";
}
else
{
cout << "0";
}
}
cout << endl;
}
int main()
{
sigset_t s, p, o;
sigemptyset(&s);
for (int i = 1; i < 32; i++)
{
sigaddset(&s, i);
sigprocmask(SIG_BLOCK, &s, &p);
cout << "前" << ": ";
printsigset(&p);
cout << "后" << ": ";
printsigset(&s);
sleep(2);
}
return 0;
}
解释代码
结果:1到31号信号都被阻塞(实际上9号和19号信号无法阻塞),以位图的形式打印出来
sigaction
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
解释:
sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0, 出错则返回- 1。signum是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oldact指针非空,则通过oldact传出该信号原来的处理动作。act和oldact指向sigaction结构体。
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);
};
例子:
使用sigaction函数自定义SIGINT信号的处理方式。要求:自定义处理函数名称为“sigcb”, 在sigcb当中完成打印信号值。
#include <signal.h>
#include <unistd.h>
#include <iostream>
using namespace std;
void sigcb(int signo)
{
while (1)
{
cout << "我是" << signo << "号信号, 我被自定义处理了" << endl;
sleep(2);
}
}
int main()
{
struct sigaction act;
act.sa_handler = sigcb;
sigaction(SIGINT, &act, NULL);
while (1)
{
cout << "我没有被自定义捕捉" << endl;
sleep(2);
}
}
结果:
五、信号怎么处理
信号什么时候被处理?
信号发出后并不是立马递达的,而是进程从内核态返回用户态的时候,才对信号进行检查与处理。