信号通信
什么是信号?
在操作系统中,当我们无正常结束一程序时,可以用任务管理器强行结束这个进程。在unix/linux 中,具体的实现过程是通过进程A 生成一个信号并发射出去,运行中的进程B捕获到这个信号然后根据这个信号的特定意义做出相应的操作。
信号是UNIX 和Linux 系统响应某些条件而产生的一个事件,接收到该信号的进程会相应地采取一些行动。信号的处理实质是能软件中断这样的机制来实现的。
通常信号是由一个错误产生的。但它们还可以作为进程间通信或修改行为的一种方式,明确地由一个进程发送给另一个进程。
信号的种类
在linux 系统中预定义了多种信号,主要分布在头文件signal.h 中,信号都以SIG 开头。
可以通过以下方式查看:#kill -l
说明:
程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP
不能恢复至默认动作的信号有:SIGILL,SIGTRAP
默认会导致进程流产的信号有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,
SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
默认会导致进程退出的信号有:SIGALRM,SIGHUP,SIGINT,SIGKILL,
SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
默认会导致进程停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
默认进程忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH
信号的工作原理
a.进程A 向内核设置进程B 信号
b.内核管理信号,并在信号成立时向进程B 发射信号
c.进程B 捕获到信号并执行响应
整个通信过程中,不同阶段使用不同的接口函数
1)发送阶段
kill 函数
raise 函数
alarm 函数
2)处理阶段
signal 函数
3)接收阶段
while(1);
sleep();
pause();
下面分别介绍信号通信过程中,每个阶段所使用到的函数接口
信号的发送
kill 函数实现向进程发送信号
头文件
#include <sys/types.h>
#include <signal.h>
函数原型
int kill(pid_t pid, int sig);
返回值
成功:0
失败:-1
参数列表
pid_t pid:要操作的进程PID
int sig : 发送的信号(信号序号或者信号名)
示例:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
int main()
{
int pid,sig;
while(1)
{
printf("please input process pid and sig:\n");
scanf("%d%d", &pid,&sig);
//发送指定信号给指定进程
kill(pid,sig);
}
return 0;
}
raise 函数用于发送信号给自己
头文件
#include <signal.h>
函数原型
int raise(int sig);
返回值
成功返回0,失败返回非0
参数
Sig:发送的信号
示例:使用raise 实现自杀
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main()
{
printf("raise before \n");
raise(9);
printf("raise after \n");
return 0;
}
运行结果:
xie@ubuntu:/mnt/hgfs/share/ISP/pratice8.9$ vi raise.c
xie@ubuntu:/mnt/hgfs/share/ISP/pratice8.9$ gcc raise.c -o raise
xie@ubuntu:/mnt/hgfs/share/ISP/pratice8.9$ ./raise
raise before
Killed
xie@ubuntu:/mnt/hgfs/share/ISP/pratice8.9$
以上代码中,执行完raise 函数后,并没有打印raise after,说明raise 设置的信号是立即有效的。
alarm 函数用于实现延时触发信号给自己。
函数说明
用来设置信号在经过参数seconds 指定的秒数后传送给目前的进程.
头文件
#include <unistd.h>
函数原型
unsigned int alarm(unsigned int seconds);
参数
seconds,定时时间,单位为秒
返回值
返回之前闹钟的剩余秒数, 如果之前未设闹钟则返回0
示例:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main()
{
int i=0;
printf("alarm before \n");
alarm(9);
printf("raise after \n");
while(i<15)
{
printf("cnt=%d\n",++i);
sleep(1);
}
return 0;
}
运行结果:
alarm before
raise after
cnt=1
cnt=2
cnt=3
cnt=4
cnt=5
cnt=6
cnt=7
cnt=8
cnt=9
Alarm clock
xie@ubuntu:/mnt/hgfs/share/ISP/pratice8.9$
pause 函数用于实现进程的休眠
头文件
#include <unistd.h>
函数原型
int pause(void);
示例:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char **argv)
{
printf("pid = %d\n", getpid());
pause();
printf("pause done \n");
return 0;
}
运行结果:
xie@ubuntu:/mnt/hgfs/share/ISP/pratice8.9$ ./pause
pid = 3125
^C //ctrl+c
xie@ubuntu:/mnt/hgfs/share/ISP/pratice8.9$
以上代码中,进程调用pause 后会一直处理休眠态,这时可以按下ctrl + C 来结束进程。
信号的处理
通过前面的学习,我们已经知道,当内核接收到信号后,会在相应时间内对进程作出操作,而这样的操作都是信号的默认操作,如
alarm 函数实现的是配置14) SIGALRM 终止进程
ctrl+c 2) SIGINT 终止进程
kill 9) SIGKILL 终止进程
ctrl+z 20) SIGTSTP 暂停进程
可是,在很多实际情况下,当触发出信号后,我们并不打算让内核来中止或者暂停我们的进程,而是中断跳出处理其他事,完了再回来这时我们该怎么办? 可以采用signal 来处理
signal 函数用于获捕信号
头文件
#include <signal.h>
函数原型
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
返回值
sighandler_t 是一个函数指针变量,含有一个整形参数,即信号编号,返回值是void
如果返回负数,则出错。
参数列表
信号signum
函数指针sighandler_t handler,是一个系统的回调函数,可取以下三种情况
a.SIG_IGN 忽略本次触发的信号
b.SIG_DFL 采用系统默认的方式处理
c.自定义的信号处理函数指针
示例:请为SIGALRM 自定义一个对应的处理函数。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void alarm_handler(int signum)
{
int i=0;
while(i<10)
{
printf("alarm handler, signum = %d\n", signum);
i++;
sleep(1);
}
return ;
}
int main(int argc, char **argv)
{
int i=0;
//添加处理函数
int ret;
ret = (int)signal(SIGALRM, alarm_handler);
if(ret < 0)
{
printf("fail to signal \n");
return -1;
}
//延迟触发SIGALRM信号
printf("alarm before \n");
alarm(10);
printf("alarm after \n");
while(i<20)
{
printf("main handler, cnt = %d\n",++i);
sleep(1);
}
while(1);
return 0;
}
运行结果:
xie@ubuntu:/mnt/hgfs/share/ISP/pratice8.9$ ./signal
alarm before
alarm after
main handler, cnt = 1
main handler, cnt = 2
main handler, cnt = 3
main handler, cnt = 4
main handler, cnt = 5
main handler, cnt = 6
main handler, cnt = 7
main handler, cnt = 8
main handler, cnt = 9
main handler, cnt = 10
alarm handler, signum = 14
alarm handler, signum = 14
alarm handler, signum = 14
alarm handler, signum = 14
alarm handler, signum = 14
alarm handler, signum = 14
alarm handler, signum = 14
alarm handler, signum = 14
alarm handler, signum = 14
alarm handler, signum = 14
main handler, cnt = 11
main handler, cnt = 12
main handler, cnt = 13
main handler, cnt = 14
main handler, cnt = 15
main handler, cnt = 16
main handler, cnt = 17
main handler, cnt = 18
main handler, cnt = 19
main handler, cnt = 20
如果把ret = (int)signal(SIGALRM, alarm_handler);
中的alarm_handler
换成SIG_IGN
(屏蔽该信号),结果将会是如下:
xie@ubuntu:/mnt/hgfs/share/ISP/pratice8.9$ ./signal
alarm before
alarm after
main handler, cnt = 1
main handler, cnt = 2
main handler, cnt = 3
main handler, cnt = 4
main handler, cnt = 5
main handler, cnt = 6
main handler, cnt = 7
main handler, cnt = 8
main handler, cnt = 9
main handler, cnt = 10
main handler, cnt = 11
main handler, cnt = 12
main handler, cnt = 13
main handler, cnt = 14
main handler, cnt = 15
main handler, cnt = 16
main handler, cnt = 17
main handler, cnt = 18
main handler, cnt = 19
main handler, cnt = 20
如果把ret = (int)signal(SIGALRM, alarm_handler);
中的alarm_handler
换成SIG_DFL
(恢复默认信号处理方式),结果将会是如下:
xie@ubuntu:/mnt/hgfs/share/ISP/pratice8.9$ ./signal
alarm before
alarm after
main handler, cnt = 1
main handler, cnt = 2
main handler, cnt = 3
main handler, cnt = 4
main handler, cnt = 5
main handler, cnt = 6
main handler, cnt = 7
main handler, cnt = 8
main handler, cnt = 9
main handler, cnt = 10
Alarm clock
xie@ubuntu:/mnt/hgfs/share/ISP/pratice8.9$