此处信号和我们之前讲的信号量没有半毛钱关系;
什么是信号呢?信号就是通知一个进程发生了什么事件,然后让这个进程停下当前操作,去处理事件。处理完毕之后接着在执行。信号实际上可以归为进程间通信的一种方式(比如僵尸进程形成的原因,子进程结束给父进程发送信号)。
在Linux下面系统定义的信号:kill -l
从图中我们可以看到有62个信号(其中没有32和33)
其中1~31是非可靠信号(非实时信号)是继承UNIX而来的。34~64是可靠信号(实时信号)Linux中添加的。
信号的生命周期(此处使用红绿灯举例)
当我们过马路交通信号变为红灯(信号产生),然后我们看到了红灯(注册到我们大脑,这时还没有处理该信号),我们接下来意识到是红灯,准备停下脚步(信号注销),停止脚步(信号处理)。
所以信号的生命周期:信号产生——>信号注册——>【信号阻塞】——>信号注销——>信号处理
1、信号的产生方式
(1)硬件中断(终端按键产生信号):例如ctrl+c、ctrl+z、ctrl+\
(2)程序异常:例如程序执行了除以0的指令,非法内存访问(对空指针解引用)
(3)系统调用函数向进程发送信号:例如kill函数、raise函数和sigqueue函数
kill函数:给指定的一个进程发送指定的信号
raise函数:给当前进程发送指定的信号(自己给自己发送)
上面这两个函数成功返回0,失败-1
abort函数:使当前进程接收到信号异常终止
和exit一样,abort函数总是会成功的,没有返回值。
sigqueue函数:给指定的进程发送指定的信号并且携带参数;
参数:
sigqueue的第一个参数是指定接收信号的进程id,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
返回值:成功返回0,失败返回-1
(4)由软件条件产生信号:alarm函数
调用alarm函数可以设定一个闹钟,在seconds秒之后会像当前进程发送SIGALRM信号,该信号的默认处理方式为终止当前进程。
返回值:0或者之前设定的闹钟时间还剩下的时间秒数。
如果seconds为0,表示取消之前设定的闹钟,并且默认退出程序,函数返回值还是之前设定的闹钟的剩余秒数或者0。
core dump(核心转储)
什么事core dump呢?当一个进程异常终止时我们会把该进程的内存空间和相关数据保存到磁盘,这个文件名通常是core,这就是core dump。一个进程异常终止通常是有bug的,事后我们通过调试core文件可以找出原因(事后调试)。一个进程可以产生多大的core文件可以通过ulimit -a修改大小。通常默认不允许产生core文件,第一点原因就是太浪费磁盘空间,第二点就是core文件中通常包含用户一些私密信息,所以不允许生成core文件。
为什么需要core dump文件进程程序调试?因为偶然性的错误不会进程发生,cgdb直接调试短时间可能遇不到这种错误,所以我们需要使用core dump文件辅助调试。
在cgdb中使用bt查看函数调用栈,core-file + core文件,即可知道错误在哪不需要调试。
2、信号的注册
一个进程需要管理所有的信号,所以就是用一张位图表管理所有的信号。
信号的注册就是把信号记录到pcb中的sigset_t pending位图,修改信号相应的位置置为1。
可靠信号注册:信号到达时吧pending位图相应位置置为1,并且在sigqueue队列中添加一个节点。当相同信号再次到达时如果pending位图位置已经为1,那么只在sigqueue队列中添加节点。
非可靠信号注册:信号到达时修改pending位图信号相应位置置为1,并且添加一个节点在sigqueue队列中。当相同的信号再次到达时,如果pending位置信号位置已经为1,则该信号直接丢弃。
3、信号的注销
信号的注销就是吧pending位图中信号相应位置置为0,并且删除sigqueue节点。
可靠信号的注销:先删除节点,然后判断是否还有相同节点,如果没有才把pending位图置为0,否则pending位图不变。
非可靠信号的注销:直接删除节点并且pending位图位置置为0。
4、信号的阻塞
进程中也是维护一张位图表来阻塞相应的信号。
信号递达:实际执行信号的处理动作称为信号递达。
信号未决:信号产生到递达之间的状态称为信号未决。
阻塞:阻止信号递达。被阻塞的信号处于信号未决状态。
进程pcb中维护有一张block表(信号屏蔽字),如果阻塞了某一个信号就把该信号位置置为1。进程看到pending中收到哪些信号,然后就要开始处理这些信号,但是在处理这些信号之前,进程会先对比一下这些信号有没有存在于blocked集合中,如果存在了,意味着这个信号现在将不被处理,直到解除阻塞。
kill -9 和kill -19 不可以阻塞
5、信号的处理
每个信号都有两个标志位阻塞(block)和未决(pending),还有一个函数指针表示信号的处理动作。
信号的处理动作分为3中(1)默认处理动作,一般默认都是终止进程(2)忽略信号,忽略并不是直接把信号丢弃。如果该信号被阻塞,那么不可以把该信号丢弃,因为信号处理方式完全可能在解除阻塞之前发生改变。(3)自定义操作。
3、信号集操作函数
(1)阻塞信号集(sigprocmask函数)
(2)信号集的一些操作
(3)读取当前进程的未决信号(sigpending函数)
就是把pending位图中的信号放到set中。
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<string.h>
int main()
{
sigset_t set;
sigset_t oldset;
sigemptyset(&set);
sigfillset(&set);
sigprocmask(SIG_BLOCK,&set,&oldset);
getchar();//用于我们输入信号
sigset_t pending;
sigemptyset(&pending);
sigpending(&pending);
int i = 1;
for(i = 1;i<32;i++)
{
if(sigismember(&pending,i))
printf("1 ");
else
printf("0 ");
}
fflush(stdout);
//sigprocmask(SIG_UNBLOCK,&set,NULL);
sigprocmask(SIG_SETMASK,&oldset,NULL);
while(1);
return 0;
}
(4)信号处理函数
//signal 信号处理方式
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
void sigcb(int signo)
{
if(signo == SIGQUIT)
{
printf("signo = 19\n");
}
else
printf("signo = 1\n");
}
int main()
{
signal(SIGINT,sigcb);//忽略ctrl+c发出的SIGINT信号
signal(SIGQUIT,sigcb);
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
深入理解sigqueue和sigaction函数:sigqueue和sigaction
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<string.h>
#include<stdlib.h>
void sigcb(int signo,siginfo_t *info,void *cur)
{
// printf("signo:%d\n",signo);
// printf("info->si_int:%d\n",info->si_int);
// printf("info->si_value.sival_int:%d\n",info->si_value.sival_int);
printf("signo:%d\n",signo);
printf("info->si_ptr:%s\n",info->si_ptr);
printf("info->si_value.sival_ptr:%s\n",info->si_value.sival_ptr);
}
int main(int argc,char *argv[2])
{
//int sigaction(int signum, const struct sigaction *act,)
// struct sigaction *oldact);
//struct sigaction {
// //sa_handler和sa_sigaction是信号处理函数,但是handler函数接口就和signal函数接口用法一样
// //如果参数sa_flags = SA_SIGINFO时,调用的处理函数是sa_sigaction,
// void (*sa_handler)(int);
//
// void (*sa_sigaction)(int, siginfo_t *, void *);
// sigset_t sa_mask;//在信号处理时,暂时想要阻塞哪些信号
// int sa_flags;
// void (*sa_restorer)(void);//不用
// };
//
struct sigaction act;
sigemptyset(&act.sa_mask);
sigfillset(&act.sa_mask);
act.sa_sigaction = sigcb;
act.sa_flags = SA_SIGINFO;
sigaction(2,&act,NULL);
while(1)
{
//int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval val;
//val.sival_int = 100;
//sigqueue(getpid(),atoi(argv[1]),val);
char *buf = "hello world!\n";
val.sival_ptr = (char *)malloc(sizeof(buf));
strcpy((char *)val.sival_ptr,buf);
sigqueue(getpid(),atoi(argv[1]),val);
sleep(2);
}
return 0;
}