一、信号
1.信号的定义
信号是在软件层面上是一种通知机制,对中断机制的一种模拟,是一种异步通信方式。
注意:
进程在运行过程中,随时可能被各种信号打断。
进程可以忽略或者去调用相应的函数去处理信号。
进程无法预测信号到达的精准时间。
2.信号的来源
1.程序执行错误,如内存访问越界,数学运算除
0
2.由其他进程发送
3.通过控制终端发送,如
ctrl + c
4.子进程结束时向父进程发送的
SIGCHLD
信号
5.程序中设定的定时器产生的
SIGALRM
信号
3 .信号的种类
在Linux系统可以通过 kill -l 命令查看.
4.信号处理流程
信号处理流程包含以下两个方面:
信号的发送 :可以由进程直接发送
信号投递与处理
:
由内核进行投递给具体的进程并处理
在
Linux
中对信号的处理方式:
忽略信号
,即对信号不做任何处理,但是有两个信号不能忽略:即
SIGKILL
及
SIGSTOP
。
捕捉信号
,定义信号处理函数,当信号发生时,执行相应的处理函数。
执行缺省操作,
Linux
对每种信号都规定了默认操作
---
在内核中的用于管理进程的结构为
task_struct
二、信号发送
当由进程来发送信号时,则可以调用
kill()
函数与
raise ()
函数。
1.kill 函数
函数原型
int kill(pid_t pid, int sig);
函数功能
向指定的进程发送一个信号
函数参数
pid:
进程的
id
sig:
信号的
id
2.raise 函数
函数原型
int raise(int sig);
函数功能
send a signal to the caller
函数参数
sig:
信号编号
3.补充
具体函数内容使用Man()手册页
实例:创建一个子进程,子进程通过信号暂停,父进程向子进程发送终止信号
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
int main()
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork");
exit(EXIT_FAILURE);
}
else if(pid==0)
{
// 执行子进程
fprintf(stdout,"child <%d> is running.\n",getpid());
// SIGSTOP 暂停一个进程,不能阻塞、处理或忽略,只能采用默认处理
raise(SIGSTOP);
fprintf(stdout,"child <%d> is exit.\n",getpid());
// 成功退出子进程
exit(EXIT_SUCCESS);
}
else
{
int ret;
sleep(1);
ret = kill(pid,SIGKILL);//给指定的进程发送kill信号,只能采用默认操作,结束进程
if(ret == 0)
{
fprintf(stdout,"parent<%d> killchild<%d>.\n",getpid(),pid);
}
waitpid(pid,NULL,0);
fprintf(stdout,"father<%d> exit.\n",getpid());
exit(EXIT_SUCCESS);
}
return 0;
}
三、等待信号
在进程没有结束时,进程在任何时间点都可以接受到信号。
需要阻塞等待信号时,则可以调用
pause() 函数。(注意:
pause
函数一定要在收到信号之前调用,让进程进入到睡眠状态 )
函数原型
int
pause
(
void
);
函数功能
阻塞进程,直到收到信号后唤醒
函数返回值
成功
:
返回
0
失败
:
返回
-
1
,
并设置
errno
四、信号处理
信号是由操作系统内核发送给指定进程,进程收到信号后则需要进行处理
处理信号有三种方式:
忽略
:
不进行处理
默认
:
按照信号的默认方式处理
用户自定义
:
通过用户实现自定义处理函数来处理,由内核来进行调用
每种信号都有相应的默认处理方式;
五、用户自定义处理
1. 实现自定义处理函数
用户实现自定义处理函数,需要按照下面的格式
typedef void (*sighandler_t)(int);
typedef void (*)(int) sighandler_t
2. 设置信号处理处理方式
通过
signal
函数设置信号处理方式
函数头文件
#include <signal.h>
函数原型
sighandler_t signal(int signum, sighandler_t handler);
函数功能
设置信号的处理方式
,
如果是自定义处理方式,提供函数地址,注册到内核中
函数参数
signum:
信号编号
handler:
信号处理方式
SIG_IGN---->
忽略信号
SIG_DFL---->
按照默认方式处理
自定义处理函数的地址
通过 cstag SIG_DFL
查看
五、、定时器信号
在
Linux
系统中提供了
alarm
函数,用于设置定时器
函数头文件
#include <unistd.h>
函数原型
unsigned int alarm(unsigned int seconds);
函数功能
设置定时器的秒数
函数参数
seconds:
定时的时间秒数
函数返回值
返回上一次进程设置定时器剩余的秒数
,
如果进程上一次没有设置定时器,则返回
0
Tips:
定时器的定时任务由内核完成的,
alarm
函数负责设置定时时间
,
并告诉内核启动定时器
当定时时间超时后,内核会向进程发出
SIGALRM
信号。
实例:
验证
alarm
函数的返回值
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
unsigned int ret;
ret = alarm(3);
printf("ret = %d\n",ret); // 进程上一次没有设置定时器,ret = 0
sleep(1);
ret = alarm(4); // ret = 2 上一次进程设置的定时器还剩下2s
printf("ret = %d\n",ret);
printf("main process end....\n");
return 0;
}
代码
2
:设置定时器的定时时间为
3s ,
并处理
SIGALRM
信号
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
void sigalrm_handler(int signum)
{
printf("sigalrm:%s\n",strsignal(signum));
}
int main()
{
unsigned int ret;
// 注册信号处理函数
if(signal(SIGALRM,sigalrm_handler) == SIG_ERR)
{
perror("signal:");
exit(EXIT_FAILURE);
}
ret = alarm(3);
pause();
printf("main process end....\n");
return 0;
}
六、子进程退出信号
在使用
wait()
函数时,由于阻塞或者非阻塞都非常消耗资源。并且在阻塞情况下,父进程不能执行其他逻辑。
如何解决?
子进程退出是异步事件,可以利用在子进程退出时,会自动给父进程发送 SIGCHLD 信号
代码1:使用信号处理僵死态进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void sigchld_handler(int signum)
{
printf("SIGCHLD handler:<%s>\n",strsignal(signum));
int status=0;
wait(&status);
}
int main()
{
pid_t pid;
if(signal(SIGCHLD,sigchld_handler)==SIG_ERR)
{
perror("signal failed.");
exit(EXIT_FAILURE);
}
pid = fork();
if(pid == -1)
{
perror("fork");
exit(EXIT_FAILURE);
}
else if(pid == 0)
{
printf("child process<%d> is running.....\n",getpid());
sleep(2);
exit(EXIT_SUCCESS);
}
else
{
while(1)
{
}
}
return 0;
}