信号的产生
Linux下信号这个概念可以来说是非常重要的,先来说下如何产生信号,然后在逐一解释:
- 键盘组合键
- 硬件异常错误
- 通过一些指令
- 软件条件
- 调用系统函数
1、键盘组合键这个很好理解,下面以一个简单的实例来说明。
#include<stdio.h>
int main()
{
printf("pid is: %d\n",getpid());
while(1);
return 0;
}
程序是一个死循环,当这个程序在运行的时候,你的其它指令是不起作用的,那么可以通过Ctrl+c和ctrl+\来终止到这个进程。这里需要注意的是,键盘组合键只对前台程序起作用。ctrl+c产生SIGINT信号,ctrl+z产生SIGTSTP信号,ctrl+\产生SIGQUIT信号。
Linux下的信号可以通过kill -l命令查看:
这些信号里面34号以上的信号都为实时信号,这里先讨论前面的基础信号。
再回到上面的例子,当一个进程收到SIGQUIT这个信号时,会终止该进程并且core dump(段错误)解释下core dump,当一个进程异常终止时,会将内存数据全部保存在磁盘上,文件名为core,那么上面的程序我们来试一下。
结果并没有产生core文件,那么看下面的东西:
通过这个命令可以查看一些内存分配问题
ulimit -a
第一行,默认core文件的大小是0,所以没有生成core文件,可以修改core的值。
ulimit -c 1024
给core文件默认分配1024大小,这样,然后再次Ctrl+\。
可见生成了core文件,后缀是该进程的PID,细心的你应该发现了,这个core文件特别的大,所以默认情况下才不会生成这个东西。
2、硬件错误的话,因为Linux下一切皆文件的思想,当你的硬件出问题后,操作系统就会收到该硬件文件发来的信号,这个没什么太大的问题。
3、 通过指令直接产生信号,比如杀死一个进程,直接调用kill -9 后面跟上要删除进程的PID,这样直接就产生信号杀死该进程。
4、由软件条件产生的信号,如SIGPIPE,SIGALRM
具体的alarm函数可以参见alarm实现sleep这篇文章
5、调用系统函数产生,下面介绍一下相关函数:
#include<signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);
kill指令其实也是通过kill函数来实现的,而raise函数可以给当前进程发送指定的信号(可以自己给自己发信号)
成功返回0值,错误返回-1;
#include<signal.h>
void abort(void);
abort函数时当前进程收到信号而异常终止,类似于exit函数一样,该函数总是会成功的,所以没有返回值。
信号集函数
接下来到了信号重点中的重点了,在实际执行信号动作时,称为信号递达。信号从产生到递达之间的状态,称为未决(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);//判断是否含有有个信号
信号屏蔽字
#include<signal.h>
int sigprocmask(int how,const sigset_t *set,sigset_t *oset);
成功返回0,错误返回-1;
how为如何更改,oset和set都是非空指针,现将原来的信号屏蔽字备份到oset里。
读取未决信号集
#include<signal.h>
int sigpending(sigset_t *set);
读取当前进程的未决信号集,成功返回0,出错返回-1。
以下面的代码来更好的理解上面这些函数:
#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);
sigprocmask(SIG_BLOCK,&s,NULL);
while(1)
{
sigpending(&p);
printsigset(&p);
sleep(1);
}
return 0;
}
这里我们阻塞了SIGINT信号,使该信号处于未决状态。所以ctrl+c并没有终止该进程,只能另起一个终端直接kill掉了。
如下:
捕捉信号
对于信号的处理动作有忽略,默认,和自定义。捕捉信号首先得先要识别该信号,并且要知道怎么处理该信号。
信号的整个捕捉过程如下:
- 用户注册SIGQUIT信号,处理sighandler
- 正在执行main函数,发生中断或异常切入内核态
- 处理完异常,返回用户态前检查有否有信号递达
- 返回用户态后不是先继续main函数,而是执行sighandler函数,由于存在不同的堆栈,所以是独立的控制流程
- sighander函数返回后执行特殊的系统调用sigreturn再次进入内核态
- 如果没有信号递达了就返回至main函数继续上下文执行
这里可以参考实现mysleep函数的实现代码理解。