这篇文章从以下几个方面来了解信号
1、什么是信号
2、信号的产生
3、信号的处理
4、发送信号
5、信号处理函数安全
1、什么是信号?
信号是一种事件通知机制,当接收到该信号的进程会执行相应的操作
2、信号的产生
- 由硬件产生,如从键盘输入Ctrl+C可以终止当前进程
- 由其他进程发送,例如,在shell进程下,使用命令kill -信号值 PID
- 异常,当进程异常时发送信号
3、信号的处理
信号是由操作系统处理的,所以信号的处理在内核态。如果不是紧急信号的话,它不一定被立即处理,操作系统不会为了处理一个信号而把当前正在运行的进程挂起,因为挂起(进程切换)当前进程消耗很大。所以操作系统一般会将信号先放入信号表中,一般选择在内核态切换回用户态的时候处理信号(不用自己单独进行进程切换以免浪费时间)
信号的处理过程图:
可以使用 kill -l 命令查看所有的信号
信号的处理方式有3种
- 忽略(就是不管你发啥我都不理你)
- 默认处理方式(会终止一个进程)
- 自定义处理方式(自己写一个函数,信号发生时执行自己的函数
下面看看自定义信号的处理方式
(1)signal函数
void(*signal(int sig,void (*func)(int)(int))
sig:信号值
func:信号处理的函数指针,参数为信号值
示例代码:发送信号后执行对数组的排序
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<signal.h>
void fun_sort(int sig)
{
int arr[6]={3,1,7,4,8,10};
int n=sizeof(arr)/sizeof(arr[0]);
int i;
int j;
int tmp;
for(i=0;i<n;i++)
{
for(j=0;j<n-i-1;j++)
{
if(arr[j]>arr[j+1])
{
tmp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=tmp;
}
}
}
for(j=0;j<n;j++)
{
printf("%d ",arr[j]);
}
printf("\n");
}
int main()
{
signal(SIGINT,fun_sort);
while(1)
{
printf("runing\n");
sleep(1);
}
exit(0);
}
运行结果:
(2)sigaction函数
int sigaction(int sig,const struct sigaction *act,struct sigaction *oact);
sig:信号值
act:指定信号的动作,相当于func
oact:保存原信号的动作
看看代码示例:
int main()
{
struct sigaction act;
act.sa_handler = fun_sort;
sigemptyset(&act.sa_mask);
//act.sa_flags=SA_RESETHAND;如果不设置,则默认情况下是不被重置的
sigaction(SIGINT,&act,0);
while(1)
{
printf("runing\n");
sleep(1);
}
}
下面是不设置act.sa_flags=SA_RESETHAND的结果,代码会一直运行,上面重置了第二次接收信号就会停止运行。
4、发送信号
Kill函数,进程可以通过kill函数向包括它本身以外的其他进程发送信号,如果没有权限就失败(原因通常是目标进程由另一个进程拥有,就是没权限的时候,总不能控制别人的程序,root会除外)
int kill(pid_t pid,int sig)
它的作用是把信号sig发送给pid进程,成功时返回0;失败原因一般存在3点:给定的信号无效、发送权限不够、目标进程不存在
代码示例:
int main()
{
pid_t pid;
pid=fork();
switch(pid)
{
case -1:
perror("fork failed\n");
case 0://子进程
sleep(5);
kill(getppid(),SIGALRM);
exit(0);
default:;
}
signal(SIGALRM,func);
while(!n)
{
printf("hello world\n");
sleep(1);
}
if(n)
{
printf("hava a signal %d\n",SIGALRM);
}
exit(0);
}
运行结果:
可以看到在进程中fork复制一个新进程,在子进程5秒后发送一个SIGALRM信号,父进程捕获这个信号后用func函数改变n的值,然后退出循环,所以结果输出5个hello world后,程序收到SIGARLM信号,结束进程。
(3)alarm函数:提供一个闹钟的功能,进程可以调用alarm函数在经过预定时间时发送一个SIGALRM信号。
函数原型:unsigned int alarm(unsigned int seconds);
注:如果父进程在子进程的信号到来之前什么也不做,可以调用pause()函数俩挂起父进程,直到接收到信号。
现在我们看看加上aralm函数和pause函数后的结果
int main()
{
pid_t pid;
signal(SIGALRM,func);
alarm(5);//调用函数,5秒后发送SIGALRM信号
pause();//挂起进程直到接收到信号
if(n)
{
printf("hava a signal %d\n",SIGALRM);
}
exit(0);
}
运行结果:5秒后收到SIGALRM信号,程序恢复运行,输出信息并退出。
5、信号处理函数的安全问题
信号处理函数时可以在其执行期间被中断并被再次调用,当返回到第一次调用时,它能继续正确执行是关键,所以信号处理的函数必须是可重入的(不论什么时候再去执行都会是相同的结果)下面是可重入函数表: