每一个信号都有一个编号和一个宏定义的名称,这些宏定义可以再signal.h中找到。编号34以上的为实时信号。
关于每个信号的作用是什么我们可以通过man指令去查看,例如:man 9 signal。其中数字对应第几号信号。
信号产生的方式:
- 用户在终端的某些按键会发送信号给前台进程。例如ctrl+c产生SIGINT信号。
- 硬件异常产生信号。例如如果访问了非法地址,内核将发送一个SIGSEGV发送给进程。
- 一个进程可以调用kill(2)函数给另外一个进程发送信号。
- 软件条件产生。
通过终端按键产生信号
- SIGINT的默认处理动作是终止进程。
- SIGQUIT的默认处理动作是终止进程并且Core Dump(当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上)。
使用系统函数向进程发送信号
在这里我们用到的函数为:int kill(pid_t pid,int signo);pid为需要发送信号的进程,signo则为索要发送的信号
kill指令的实现就是调用kill函数,调用指令方式为kill -11 1234;这里11为信号编号,也可以不写编号直接写信号名,1234为进程id。
#include<stdio.h>
int main()
{
printf("get pid = %d\n",getpid());
while(1);
return 0;
}
我们在这里写一个死循环,然后启用另一个终端利用kill函数将它关闭。
在这里我们也可以了解一下另外两个函数
int raise(int signo);这个函数与kill函数唯一的区别是只能给自己发信号,且返回值成功为0,失败为1。
void abort(void);这个函数可以使当前进程接收到信号而终止异常。
由软件条件产生信号
这里我们先了解一个函数
unsigned int alarm(unsigned int seconds);
调用这个函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发送一个SIGALRM信号,该信号会终止当前进程。这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。
#include<stdio.h>
#include<unistd.h>
int main()
{
int i = 1;
alarm(1);
for(;1;i++)
printf("i = %d ",i);
return 0;
}
我们来用这个程序了解一下alarm函数的作用。
由结果可以看出,在闹钟提醒前代码正常运行,但当alarm函数运行后进程立即被打断了。
阻塞信号
要了解阻塞信号我们要先了解一下信号和其他的一些常见的概念
- 实际执行信号的处理动作成为信号递达。
- 信号从产生到递达之间的状态,成为信号未决。
- 进程可以阻塞某个信号。
- 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞。
- 阻塞和忽略是不同的,信号被阻塞不会被递达,而忽略是收到信号后的一种动作。
每个信号都有两个标志位分别表示阻塞(block)和未决,还有一个函数指针表示接收到该信号后的动作。
对于图中SIGHUP信号,因为未产生也不阻塞,所以若该信号产生则进行默认操作。
对于图中SIGINT信号,虽然产生过但被阻塞了,所以并不会被递达。
对于图中SIGQUIT信号,一旦产生就会被阻塞,不能递达。
因为每个信号只有一个bit的未决标志,非0即1,阻塞标志也是这样的,因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储。
* 信号集操作函数
sigprocmask
调用此函数可以读取或更改进程的信号屏蔽字。
int sigprocmask(int how,const sigset_t *set,sigset_t *oset)返回值成功为0,失败为-1。
sigpending
调用此函数可以读取当前进程的未决信号集,通过set参数传出,调用成功返回0,失败返回-1。
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void printsigset(sigset_t* p)
{
int i = 0;
for(;i<32;i++)
{
if(sigismember(p,i))
putchar('1');
else
putchar('0');
}
puts("");
}
int main()
{
sigset_t p,s;
sigemptyset(&s);
sigaddset(&s,SIGINT);
sigprocmask(SIG_BLOCK,&s,NULL);
while(1)
{
sigpending(&p);
printsigset(&p);
sleep(1);
}
return 0;
}
在这段代码中我们将SIGINT信号屏蔽掉,运行后看一下结果。
由运行结果可以看出,crtl+c已经不能结束进程了,因为这个信号已经被屏蔽掉了。
信号捕捉
当前正在执行函数时,这时发生异常或者中断进入到了内核态,中断处理完后,这时如果检测到有信号递达,内核就会在返回到用户态后优先去执行该信号对应的函数,在执行完成后会继续进入到内核态,这时如果没有新的信号递达,内核才会恢复main函数的上下文继续执行。
可重入函数
只访问自己局部变量或者参数的函数称为可重入函数,可能因为重入导致错误的函数称为不可重入函数。