目录
什么是信号
信号是在软件层面上是一种通知机制,对中断机制的一种模拟,是一种异步通信方式。一般具有如下特点:
进程在运行过程中,随时可能被各种信号打断
进程可以忽略或者去调用相应的函数去处理信号
进程无法预测信号到达的精准时间
在 Linux 中信号一般的来源如下:
程序执行错误,如内存访问越界,数学运算除0
由其他进程发送
通过控制终端发送,如ctrl + c
子进程结束时向父进程发送的SIGCHLD信号
程序中设定的定时器产生的SIGALRM信号
信号的种类
在Linux系统中 kill -l 可查看
SIGINT 该信号在用户键入INTR字符(通常是Ctrl-C)时发出,终端驱动程序发送此信号并送到前台进
程中的每一个进程。
SIGQUIT 该信号和SIGINT类似,但由QUIT字符(通常是Ctrl-)来控制。
SIGILL 该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、
堆栈溢出时)发出。
SIGFPE 该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为0
等其它所有的算术的错误。
SIGKILL 该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。
SIGALRM 该信号当一个定时器到达的时候发出。
SIGSTOP 该信号用于暂停一个进程,且不能被阻塞、处理或忽略。
SIGTSTP 该信号用于交互停止进程,用户可键入SUSP字符时(通常是Ctrl-Z)发出这个信号。
SIGCHLD 子进程改变状态时,父进程会收到这个信号
SIGABRT 进程异常中止
信号处理流程
信号处理流程包含以下两个方面:
信号的发送 :可以由进程直接发送
信号投递与处理 : 由内核进行投递给具体的进程并处理
在 Linux 中对信号的处理方式如下
忽略信号,即对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
捕捉信号,定义信号处理函数,当信号发生时,执行相应的处理函数。
执行缺省操作,Linux对每种信号都规定了默认操作
信号的发送
当由进程来发送信号时,则可以调用 kill() 函数与 raise () 函数
kill
函数头文件
#include <sys/types.h>
#include <signal.h>
函数原型
int kill(pid_t pid, int sig);
函数功能
向指定的进程发送一个信号
函数参数
pid:进程的id
sig:信号的id
raise
函数头文件
#include <sys/types.h>
#include <signal.h>
函数原型
int raise(int sig);
函数功能
send a signal to the caller
函数参数
sig:信号编号
代码示例
#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> kill
child<%d>.\n",getpid(),pid);
}
waitpid(pid,NULL,0);
fprintf(stdout,"father<%d> exit.\n",getpid());
exit(EXIT_SUCCESS);
}
return 0;
}
等待信号
在进程没有结束时,进程在任何时间点都可以接受到信号
需要阻塞等待信号时,则可以调用 pause() 函数
函数头文件
#include <unistd.h>
函数原型
int pause(void);
#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());
//子进程向父进程发送SIGUSR1信号,SIGUSR1信号的默认处理是终止进程
sleep(2);
kill(getppid(),SIGUSR1);
fprintf(stdout,"child <%d> is exit.\n",getpid());
// 成功退出子进程
exit(EXIT_SUCCESS);
}
else
{
int ret;
sleep(1);
pause();//等待信号,让进程阻塞直到收到信号
fprintf(stdout,"father<%d> exit.\n",getpid());
exit(EXIT_SUCCESS);
}
return 0;
}
提醒:pause函数一定要在收到信号之前调用,让进程进入到睡眠状态
信号处理
信号是由操作系统内核发送给指定进程,进程收到信号后则需要进行处理
处理信号有三种方式:
忽略 : 不进行处理
默认 : 按照信号的默认方式处理
用户自定义 : 通过用户实现自定义处理函数来处理,由内核来进行调用
每种信号都有相应的默认处理方式:
进程退出
SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
进程忽略
SIGCHLD,SIGPWR,SIGURG,SIGWINCH
进程暂停
SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
用户自定义处理
当有信号时,我们可以自由设定进程该怎样处理
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---->按照默认方式处理
自定义处理函数的地址
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
// 自定义处理函数
void signal_handler(int signum)
{
printf("%s\n",strsignal(signum));
}
int main()
{
// 注册处理函数的地址到内核
if(signal(SIGUSR1,signal_handler)==SIG_ERR)
{
perror("signal error.");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if(pid==-1)
{
perror("fork");
exit(EXIT_FAILURE);
}
else if(pid == 0)
{
printf("child process <%d> is running...\n",getpid());
pause();// 等待信号唤醒
printf("child process exit\n");
exit(EXIT_SUCCESS);
}
else if(pid > 0)
{
sleep(1);
int ret = kill(pid,SIGUSR1);
if(ret == 0)
{
printf("kill signum<%d> to process<%d> is
successful.\n",SIGUSR1,pid);
}
waitpid(pid,NULL,0);
}
return 0;
}
信号定时器
有时信号间处理太快,不是我们想要的结果
这时Linux系统中提供了 alarm 函数,用于设置定时器
函数原型
unsigned int alarm(unsigned int seconds);
seconds:定时的时间秒数
函数返回值
返回上一次进程设置定时器剩余的秒数,如果进程上一次没有设置定时器,则返回0
定时器的定时任务由内核完成的,alarm 函数负责设置定时时间,并告诉内核启动定时器
当定时时间超时后,内核会向进程发出 SIGALRM 信号
设置定时器的定时时间为 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 信号
示例代码:使用信号处理僵死态进程
#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;
}