信号
什么是信号?信号是给程序提供一种可以处理异步事件的方法,它利用软件中断来实现。不能自定义信号,所有信号都是系统预定义的。
信号由谁产生?
- 由shell终端根据当前发生的错误(段错误、非法指令等)Ctrl+c而产生相应的信号
比如:
socket通信或者管道通信,如果读端都已经关闭,执行写操作(或者发送数据),
将导致执行写操作的进程收到SIGPIPE信号(表示管道破裂)
该信号的默认行为:终止该进程。
2. 在shell终端,使用kill或killall命令产生信号
常见的信号
-------------------------------------------
信号名称 说明
-------------------------------------------
SIGABORT 进程异常终止
SIGALRM 超时告警
SIGFPE 浮点运算异常
SIGHUP 连接挂断
SIGILL 非法指令
SIGINT 终端中断 (Ctrl+C将产生该信号)
SIGKILL *终止进程
SIGPIPE 向没有读进程的管道写数据
SIGQUIT 终端退出(Ctrl+\将产生该信号)
SIGSEGV 无效内存段访问
SIGTERM 终止
SIGUSR1 *用户自定义信号1
SIGUSR2 *用户自定义信号2
-------------------------------------->以上信号如果不被捕获,则进程接受到后都会终止!
SIGCHLD 子进程已停止或退出
SIGCONT *让暂停的进程继续执行
SIGSTOP *停止执行(即“暂停")
SIGTSTP 中断挂起
SIGTTIN 后台进程尝试读操作
SIGTTOU 后台进程尝试写
-------------------------------------------
信号的处理
1. 忽略此信号
2. 捕捉信号,指定信号处理函数进行处理
3. 执行系统默认动作,大多数都是终止进程
信号的捕获
信号的捕获,是指,指定接受到某种信号后,去执行指定的函数。
注意:SIGKILL和SIGSTOP不能被捕获,即,这两种信号的响应动作不能被改变。
Example1:
main函数中,signal函数中存放指定的信号编号和函数指针,当捕获到该信号时,将执行函数指针所指向的函数。
由于该函数改变了收到SIGINT信号(即ctrl + c)的行为(即原来的结束进程),故当程序启动时,再次按下ctrl + c,进程不会结束,而是执行myhandle函数。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void myhandle(int sig)
{
printf("Catch a signal : %d\n", sig);
}
int main(void)
{
signal(SIGINT, myhandle);
while (1) {
sleep(1);
}
return 0;
}
signal函数的具体含义如下图所示。
Example2:
在Example1的基础上,在myhandle()函数中添加了一句
signal(SIGINT,SIG_DFL);
该段程序在首次捕捉到SIGINT信号后,不会结束进程,而是进入myhandle(),执行相关操作,在myhandle()中,又重新将捕获到SIGINT信号后的操作恢复默认,即结束进程。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void myhandle(int sig)
{
static int cnt = 0;
printf("Catch a signal : %d\n", sig);
signal(SIGINT, SIG_DFL); //等同于signal(sig, SIG_DFL);
}
int main(void)
{
signal(SIGINT, myhandle);
while (1) {
sleep(1);
}
return 0;
}
使用sigaction (项目实战强烈推荐使用)
sigaction与signal的区别: sigaction比signal更“健壮”,建议使用sigaction
用法:man 2 sigaction
结构:struct sigaction
struct sigaction {
void (*sa_handler)(int); /* 信号的响应函数 */
sigset_t sa_mask; /* 屏蔽信号集 */
int sa_flags; /* 当sa_flags中包含 SA_RESETHAND时,接受到该信号并调用指定的信号处理函数执行之后,把该信号的响应行为重置为默认行为SIG_DFL */
...
}
补充:
当sa_mask包含某个信号A时,则在信号处理函数执行期间,如果发生了该信号A,
则阻塞该信号A(即暂时不响应该信号),直到信号处理函数执行结束。
即,信号处理函数执行完之后,再响应该信号A
Example3:
其中sigemptyset()函数是用于清空屏蔽信号集的掩码,相当于初始化的作用。
若在该函数中,将sa_flags设为SA_RESETHAND
,
则与Example2的功能类似,捕捉相应信号对应的函数只执行一次,往后遍恢复默认设置。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void myhandle(int sig)
{
printf("Catch a signal : %d\n", sig);
}
int main(void)
{
struct sigaction act;
act.sa_handler = myhandle;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT, &act, 0);
while (1) {
}
return 0;
}
信号的发送
信号的发送方式:
在shell终端用快捷键产生信号
使用kill,killall命令。
使用kill函数和alarm函数
使用kill函数
给指定的进程发送指定信号
用法:man 2 kill
注意:
给指定的进程发送信号需要“权限”:
普通用户的进程只能给该用户的其他进程发送信号
root用户可以给所有用户的进程发送信号
kill失败
失败时返回-1
失败原因:
权限不够
信号不存在
指定的进程不存在
Example4:
创建一个子进程,子进程每秒中输出字符串“child process work!",父进程等待用户输入,如果用户按下字符A, 则向子进程发信号SIGUSR1, 子进程的输出字符串改为大写; 如果用户按下字符a, 则向子进程发信号SIGUSR2, 子进程的输出字符串改为小写
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
int workflag = 0;
void work_up_handle(int sig)
{
workflag = 1;
}
void work_down_handle(int sig)
{
workflag = 0;
}
int main(void)
{
pid_t pd;
char c;
pd = fork();
if (pd == -1) { //子线程创建失败
printf("fork error!\n");
exit(1);
} else if (pd == 0) { //子线程创建成功,子线程执行如下程序
char *msg;
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = work_up_handle;
sigemptyset(&act.sa_mask);
sigaction(SIGUSR1, &act, 0); //SIGUSR1信号与work_up_handle函数挂钩
act.sa_handler = work_down_handle;
sigaction(SIGUSR2, &act, 0); //SIGUSR2信号与work_down_handle函数挂钩
while (1) {
if (!workflag) {
msg = "child process work!";
} else {
msg = "CHILD PROCESS WORK!";
}
printf("%s\n", msg);
sleep(1);
}
} else { //父进程执行如下程序
while(1) {
c = getchar();
if (c == 'A') { //若输入的是‘A’,则产生SIGUSR1信号
kill(pd, SIGUSR1);
} else if (c == 'a') { //若输入的是‘a’,则产生SIGUSR2信号
kill(pd, SIGUSR2);
}
}
}
return 0;
}
Example5:
创建一个子进程,子进程在5秒钟之后给父进程发送一个SIGALR,父进程收到SIGALRM信号之后,“闹铃”(用打印模拟)
getppid():获取父进程编号。
pause():把该进程挂起,并阻塞,直到收到任意一个信号,才继续往下执行。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
int wakeflag = 0;
void wake_handle(int sig)
{
wakeflag = 1;
}
int main(void)
{
pid_t pd;
char c;
pd = fork();
if (pd == -1) {
printf("fork error!\n");
exit(1);
} else if (pd == 0) {
sleep(5);
kill(getppid(), SIGALRM); //getppid() 获得父进程编号
//向父进程发送SIGALRM信号
} else {
struct sigaction act;
act.sa_handler = wake_handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask); //清空掩码
sigaction(SIGALRM, &act, 0); //接收到SIGALRM信号后,调用act指定的处理函数
pause(); //把该进程挂起,直到收到任意一个信号
if (wakeflag) {
printf("Alarm clock work!!!\n");
}
}
return 0;
}
alarm函数
作用:在指定时间之内给该进程本身发送一个SIGALRM信号。
用法:man 2 alarm
注意:时间的单位是“秒”
实际闹钟时间比指定的时间要大一点。
如果参数为0,则取消已设置的闹钟。
如果闹钟时间还没有到,再次调用alarm,则闹钟将重新定时
每个进程最多只能使用一个闹钟。
返回值:
失败:返回-1
成功:返回上次闹钟的剩余时间(秒)
Example6:给自己的进程发信号
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
int wakeflag = 0;
void wake_handle(int sig)
{
wakeflag = 1;
}
int main(void)
{
int ret;
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = wake_handle;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM, &act, 0);
printf("time =%ld\n", time((time_t*)0));
ret = alarm(5); //过5s,发送一次alarm信号
if (ret == -1) {
printf("alarm error!\n");
exit(1);
}
//挂起当前进程,直到收到任意一个信号
pause();
if (wakeflag) {
printf("wake up, time =%ld\n", time((time_t*)0));
}
return 0;
}