信号是进程之间事件异步通知的一种方式,属于软件中断
信号的种类:
用 kill -l 命令可以查看系统定义的信号列表
每个信号都有一个编号和一个宏定义,这些信号各自在什么条件下产生,默认的处理动作,可以使用命令man 7 signal 查看。
通过终端按键产生信号
Core Dump:
kill -[信号] 进程pid
例:在后台运行一个死循环进程,然后在另一个终端发送终止信号
可以看到进程也被终止了。
int kill(pid_t pid, int sig)
函数说明:
代码示例:
#include
#include
#include
int main()
{
//给进程发送2号信号
kill(getpid(),2);
// kill(getpid(),SIGINT); 可以直接使用数字,也可以传宏定义
// raise(2); //这两个函数都是成功返回0,失败返回-1
while(1)
{
printf("linux\n");
sleep(1);
}
return 0;
}
运行结果:
[test@localhost signal_test1]$ ./signal_test2
[test@localhost signal_test1]$
可以看到并没有打印,而是直接被终止了。
同样的除了kill函数给指定进程发送信号外,raise函数可以给当前进程发送指定的信号(即自己给自己发送信号)
void abort(void)
说明:
当进程收到一个非可靠信号:
当进程收到一个可靠信号:
将该信号的sigqueue节点从sigqueue队列当中进行出队操作
需要判断sigqueue队列当中是否还有相同的sigqueue节点
收到信号,采取默认的处理方式
代码示例:
#include
#include
#include
#include
#include
void sigcb(int signo)
{
//等待子进程退出
wait(NULL);
printf("wait success\n");
}
int main()
{
signal(SIGCHLD,sigcb);
pid_t pid = fork();
if( pid < 0 )
{
perror("fork");
return -1;
}else if(pid == 0)
{
//child
sleep(5);
}else{
//parent
while(1)
{
printf("I am parent\n");
sleep(1);
}
}
return 0;
}
运行结果:
可以看到,在wait success之后,子进程没有成为僵尸进程
收到信号,不去处理
可以更改信号的处理动作
函数:
typedef void (*sighandler_t)(int)
sighandler_t signal(int signum, sighandler_t handler)
代码示例:
#include
#include
#include
//改变了原有的处理方式
void sigcallback(int signo)
{
printf("signo:%d\n",signo);
}
int main()
{
//自定义信号捕捉函数
signal(2,sigcallback); //当捕捉到2号信号时,调用sigcallback函数
signal(20,sigcallback); //当捕捉到20号信号时,调用sigcallback函数
while(1)
{
printf("linux\n");
sleep(1);
}
return 0;
}
可以更改信号的处理动作
函数:
函数说明:
signum:待更改的信号的值
struct sigaction:
act:将信号处理之前改变为act
oldact:信号之前的处理方式
代码示例:
#include
#include
#include
#include
void sigcallback(int signo)
{
printf("signo:%d\n",signo);
}
int main()
{
//sigaction
struct sigaction act;
//将位图比特位清零
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
//捕捉到信号需要调用的函数
act.sa_handler = sigcallback;
struct sigaction oldact;
// sigaction(2,&act,NULL);
//oldact是一个出参,保存的是信号原来的处理方式
sigaction(3,&act,&oldact);
getchar();
// 又将信号处理方式改回去
sigaction(3,&oldact,NULL);
while(1)
{
printf("linux\n");
sleep(1);
}
return 0;
}
调用系统调用函数的时候,或者调用库函数的时候(库函数大多数都是封装系统调用的函数的),会进入到内核空间
流程图示:
相较于sig位图,阻塞是一个block位图
函数:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
函数说明:
how:告诉sigprocmask函数应该做什么操作
set:用来设置阻塞位图
oldset:原来的阻塞位图
代码示例:
#include
#include
#include
#include
#include
int main()
{
sigset_t set;
sigset_t oldset;
//将所有信号比特位清零
sigemptyset(&set);
//将2号信号的比特位置为1
sigaddset(&set,2);
//阻塞2号信号
sigprocmask(SIG_BLOCK,&set,&oldset);
getchar();
//解除阻塞
sigprocmask(SIG_UNBLOCK,&set,NULL);
while(1)
{
printf("linux\n");
sleep(1);
}
return 0;
}
运行结果:
可以看到,在其他终端发送了2号信号后,信号被阻塞了,进程没有被终止,在按回车后,进程立即终止了,也就是说,阻塞并没有干扰信号的注册。
#include
#include
#include
#include
#include
void sigcallback(int signo)
{
printf("signo:%d\n",signo);
}
int main()
{
signal(3,sigcallback);
signal(40,sigcallback);
sigset_t set;
//将所有信号阻塞 将比特位全部置为1
sigfillset(&set);
sigset_t oldset;
sigprocmask(SIG_SETMASK,&set,&oldset);
getchar();
sigprocmask(SIG_SETMASK,&oldset,NULL);
while(1)
{
printf("linux\n");
sleep(1);
}
return 0;
}
运行结果:
可以看到分别发送可靠信号和非可靠信号各5次,可靠信号被处理了5次,而非可靠信号只处理了1次。这也就验证了不论之前sigqueue队列当中是否存在该信号的sigqueue节点,都再次添加sigqueue节点到sigqueue队列当中
代码示例:
#include
#include
#include
#include
#include
//volatile关键字保证每次取值都从内存中取值
//volatile int g_val = 1;
int g_val = 1;
void sigcallback(int signo)
{
g_val=0;
printf("signo:%d\n",signo);
}
int main()
{
signal(2,sigcallback);
//优化后一直从寄存器中取值,而不是内存中最新的值
while(g_val)
{
}
return 0;
}