linux学习:信号初识
Posted mbf330
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux学习:信号初识相关的知识,希望对你有一定的参考价值。
文章目录
信号
信号的概念:信号是进程之间事件异步通知的一种方式,属于软中断
信号列表查看
用kill -l命令可以察看系统定义的信号列表
编号34以上的是实时信号(可靠信号)
编号32以下的是非实时信号(不可靠信号)
早期Unix系统只定义了31种信号,前31种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL +C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后31个信号表示实时信号,也叫可靠信号。这保证了发送的多个实时信号都被接收。
不可靠信号的主要问题是信号可能丢失;可靠信号克服了信号可能丢失的问题。
信号的产生
1.终端按键产生信号
例如:
ctrl+c产生SIGINT(二号信号)默认处理动作是终止进程。
ctrl+\\产生SIGQUIT(三号信号)默认处理动作是终止进程并且Core Dump。
Core Dump:当一个进程要异常终止时,可以选择把进程用户空间内存数据全部 保存到磁盘上,文件名通常是core,这叫做Core Dump。进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存 在PCB中)。默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件。首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K:(修改core文件容量:$ ulimit -c 1024)
2.调用系统函数向进程发信号
kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前进程发送指定的信号(自己给自己发信号)。
11960是test进程的id。再次回车才显示 Segmentation fault ,是因为11960进程终止掉之前已经回到了Shell提示符等待用户输入下一条命令,Shell不希望Segmentation fault信息和用户的输入交错在一起,所以等用户输入命令之后才显示。
上面的命令还可以写成 kill -11 11960, 11是信号SIGSEGV的编号。以往遇到的段错误都是由非法内存访问产生的,而这个程序本身没错,给它发SIGSEGV也能产生段错误。
3.由软件条件产生信号
以alarm函数 和SIGALRM信号为例
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
//调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后
//给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。
#include<stdio.h>
#include<unistd.h>
int main()
int count=0;
alarm(1);
for(;1;count++)
printf("count = %d\\n",count);
return 0;
这个程序的作用是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终止。
4.硬件异常产生信号
硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。
信号捕捉初识
简单的信号捕捉模拟代码实现
9号信号不能被捕捉
#include <stdio.h>
#include <signal.h>
void handler(int sig)
//终端键入信号时打印catch a sig 以及该信号号码
printf("catch a sig : %d\\n", sig);
int main()
//由终端按键产生2号(ctrl+c)信号时,执行函数handler
signal(2, handler);
while(1);
return 0;
阻塞信号
1.信号相关概念
1.实际执行信号的处理动作称为信号递达(Delivery)。
2.信号从产生到递达之间的状态,称为信号未决(Pending)。
3.进程可以选择阻塞 (Block )某个信号。
4.被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达动作。
5.阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
2.在内核中的表示
每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。
block:记录信号被阻塞/ 屏蔽:是谁?是否屏蔽?
pending:保存信号:是谁?是否接收?
handler:信号处理方法
1.SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
2.SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
3.SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
3.sigset_t
从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。未决和阻塞标志可以用相同的数据类型sigset_t(信号集)来存储,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。
4.信号集操作函数
#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);
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
int sigpending(sigset_t *set);
1.函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
2.函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。
3.初始化sigset_t变量之后在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信信号屏蔽字号。
这四个函数都是成功返回0,出错返回-1。
4.sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。
5.调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。若成功则为0,若出错则为-1。
how参数取值如下:
SIG_BLOCK: 新屏蔽字是当前屏蔽字与set信号集的并集。
SIG_UNBLOCK: 新屏蔽字是当前屏蔽字与set信号集的交集。
SIG_SETMASK: 新屏蔽字是set指向的值。
6.sigpending读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。
5.模拟实现
将2号信号屏蔽,在键入2号信号时它永远不会被递达,进入死循环while之后首先32个信号全为0,键入2号信号后第二位显示为1,表示已经被屏蔽
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void show_pending(sigset_t *pending)
int sig=1;
for(;sig<=31;sig++)
//如果阻塞,返回1,未阻塞返回0;
if(sigismember(pending,sig))
printf("1");
else
printf("0");
printf("\\n");
int main()
sigset_t pending;
//将2号信号屏蔽,在键入2号信号时它永远不会被递达,
//进入死循环while之后首先32个信号全为0,键入2号信号后第二位显示为1,表示已经被屏蔽
sigset_t block,oblock;
//初始化
sigisemptyset(&block);
sigisemptyset(&oblock);
//在信号集block中添加2号信号
sigaddset(&block,2);
//设置信号屏蔽字,并返回老的信号屏蔽字
sigprocmask(SIG_SETMASK,&block,&oblock);
while(1)
//初始化
sigemptyset(&pending);
//读取未决信号集
sigpending(&pending);
show_pending(&pending);
sleep(1);
return 0;
在上面代码的基础上进行修改,让2号信号在十秒后恢复屏蔽状态为0(未屏蔽状态)并捕捉,可以看到2号信号屏蔽状态显示为0
void handler(int sig)
printf("catch a sig!:%d\\n",sig);
sleep(1);
int main()
sigset_t pending;
sigset_t block,oblock;
sigemptyset(&block);
sigisemptyset(&oblock);
sigaddset(&block,2);
sigprocmask(SIG_SETMASK,&block,&oblock);
int count=0;
while(1)
signal(2,handler);
sigemptyset(&pending);
sigpending(&pending);
show_pending(&pending);
sleep(1);
count++;
if(count==10)
printf("recover signal mask 2!\\n");
sigprocmask(SIG_SETMASK,&oblock,NULL);
return 0;
捕捉信号(自定义捕捉)
1.定义
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。
2.信号的捕捉
信号被处理,检测,递达的时间点为:系统从内核态切换为用户态时
信号处理需要四次状态切换
(1)信号捕捉在内核中的实现
例如:用户程序注册了SIGQUIT信号的处理函数sighandler。当前正在执行main函数,这时发生中断或异常切换到内核态。在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。
(2) sigaction
和signal函数的作用一样
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回-1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体。
代码实现
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handler(int sig)
printf("catch a sig : %d\\n",sig);
int main()
struct sigaction act,oact;
act.sa_handler=handler;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
sigaction(2,&act,&oact);
while(1);
//signal(2,handler);
//while(1);
//int count=0;
//alarm(1);
//binfor(;1;count++)
//
// printf("count = %d\\n",count);
//
return 0;
volatile
volatile 作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作
键入2号信号时,quit已经改为1,while()循环跳出,打印end process
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
int quit=0;
void handler(int sig)
quit=1;
printf("pid:%lu,get a sig:%d\\n",getpid(),sig);
int main()
signal(2,handler);
while(!quit);
printf("end process!\\n");
return 0;
在进行-O2或-O3优化后,quit在系统中在内存中被修改为1,main函数执行流将quit优化至cpu寄存器中,while()读取的是cpu中quit的值,仍为0
将quit置为volatile定义的变量,-O2优化,键入2号信号后直接退出,证明volatile定义的变量只能在内存中进行修改,不允许优化
volatile int quit=0;
void handler(int sig)
quit=1;
printf("pid:%lu,get a sig:%d\\n",getpid(),sig);
int main()
signal(2,handler);
while(!quit);
printf("end process!\\n");
return 0;
以上是关于linux学习:信号初识的主要内容,如果未能解决你的问题,请参考以下文章