信号实现进程间的通信
3.5.1.什么是信号
3.5.1.1、信号是内容受限(只是一个int型的数字)的一种异步通信机制
(1)信号的目的:用来通信(进程与进程之间的通信)
(2)信号是异步的(对比硬件中断),信号好像就是一种软件中断。
(3)信号本质上是int型数字编号(事先定义好的)
3.5.1.2、信号由谁发出
(1)用户在终端按下按键
(2)硬件异常后由操作系统内核发出信号
(3)用户使用kill命令向其他进程发出信号
(4)某种软件条件满足后也会发出信号,如alarm闹钟时间到会产生SIGALARM信号,向一个读端已经关闭的管道write时会产生SIGPIPE信号
3.5.1.3、信号由谁处理、如何处理
(1)忽略信号
(2)捕获信号(信号绑定了一个函数)
(3)默认处理(当前进程没有明显的管这个信号,默认:忽略或终止进程)
3.5.2.常见信号介绍(信号的名字 )
信号宏 num 信号对应的作用
(1)【SIGINT 】 2 Ctrl+C时OS送给【前台】进程组中【每个】进程
(2)SIGABRT 6 调用abort函数,进程异常终止
(3)【SIGPOLL / SIGIO 】 8 指示一个异步IO事件,在高级IO中提及
(4)【SIGKILL】 9 杀死进程的终极办法
(5)SIGSEGV 11 无效存储访问时OS发出该信号
(6)【SIGPIPE 】 13 涉及(异步通信的)管道和socket
(7)【SIGALARM】 14 涉及alarm函数的实现
(8)SIGTERM 15 kill命令发送的OS默认终止信号
(9)【SIGCHLD 】 17 子进程终止或停止时OS向其父进程发此信号等待父进程回收
以上所有信号的作用都是事先定义好的。
(10)
SIGUSR1 10 用户自定义信号,作用和意义由应用自己定义
SIGUSR2 12
这两个名称是预先已经定义好了的,但是作用是用户自己定义的。
3.5.3.进程对信号的处理
3.5.3.1、signal函数介绍
signal()函数理解
在<signal.h> 这个头文件中。
signal(参数1,参数2);
参数1:我们要进行处理的信号。系统的信号我们可以再终端键入 kill -l查看(共64个)。其实这些信号就是系统定义的宏。
[email protected]:/mnt/hgfs/Winshare/1.Linux应用编程和网络编程/7.thread# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
参数2:我们处理这些信号的方式(是系统默认还是忽略还是捕获)。
一般有3中方式进行操作。
1)eg: signal(SIGINT ,SIG_IGN ); //忽略处理
//SIG_ING 代表忽略SIGINT信号,SIGINT信号代表由InterruptKey产生,通常是CTRL +C 或者是DELETE 。发送给所有ForeGround Group的进程。
2)eg: signal(SIGINT ,SIG_DFL ); //默认处理(当前进程没有明显的管这个信号,默认:忽略或终止进程)
//SIGINT信号代表由InterruptKey产生,通常是CTRL +C或者是DELETE。发送给所有ForeGroundGroup的进程。 SIG_DFL代表执行系统默认操作,其实对于大多数信号的系统默认动作时终止该进程。这与不写此处理函数是一样的。
3)捕获信号并处理(信号绑定了一个函数)
3.5.3.2、用signal函数处理SIGINT信号
(1)忽略处理
#include<stdio.h>
#include<signal.h>
int main(void)
{
signal(SIGINT ,SIG_IGN );
while(1)
{
printf("hello world!\n");
sleep(1);
}
return 0;
}
(2)默认处理
signal(SIGINT ,SIG_DFL )
#include<stdio.h>
#include<signal.h>
int main(void)
{
signal(SIGINT ,SIG_DFL );
while(1)
{
printf("hello world!\n");
sleep(1);
}
return 0;
}
-----------------------------------------------------------------------------------------------------------------------------------------------------------
(3)捕获处理
signal函数原型:
#include <signal.h>
typedef void (*sighandler_t)(int); //typedef sighandler_t这个函数指针变量类型,重命名一种类型
sighandler_t signal(int signum, sighandler_t handler);//第二个参数是一个函数指针类型的变量,将来传进去一个函数名;返回一个函数指针。
细节:
(1)signal函数绑定一个捕获函数后信号发生后会自动执行绑定的捕获函数,并且把信号编号s数字作为传参传给捕获函数
(2)signal的返回值在出错时为SIG_ERR,绑定成功时返回指向我们自定义的捕获函数的函数指针。
(3)signal()函数(它自己是带两个参数,一个整型的信号编号,以及一个指向用户自定义的信号处理函数的指针。),而这个signal()函数的返回值也为一个函数指针,【这个函数指针指向一个带一个整型参数,并且返回值为void的一个函数.】(也就是说signal()函数的返回值和我们向它里面传递的第二个参数的类型是一样的,都是一个函数指针类型的,且这个函数指针指向的函数的返回值为void,参数为int)【相当于我们在signal函数的两个接收参数中第二个参数是向系统中【绑定】这个要执行的函数】,等到我们第一个参数中注册的信号真的发生了之后,我们signal函数的返回值就指向了我们用户自定义的函数去执行它。
(4)signal函数的返回值,它的返回值为一个函数指针,如果signal函数执行错误,则返回SIG_ERR,如果执行成功则返回一个指向自定义函数的函数指针。
(5)等于说我们在第二个参数中是向内核注册(绑定)我们的自定义函数,而当信号真的发生后,返回一个指向我们自定义函数的函数指针,是去执行我们的绑定的自定义函数。
代码示例:
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
typedef void (*sighandler_t)(int); //重新声明一个函数指针sighandler_t
void func(int sig)
{
if (SIGINT == sig)
printf("func for signal: %d.\n", sig);
exit(-1) ;
}
int main(void)
{
sighandler_t ret ; //ret为一个函数指针变量,指向一个函数
ret=signal(SIGINT, func); //相当于先向系统中注册这个函数,SIGINT是Ctrl+C时OS送给【前台】进程组中【每个】进程
//signal(SIGINT, SIG_DFL); // 指定信号SIGINT为默认处理
//ret = signal(SIGINT, SIG_IGN); // 指定信号SIGINT为忽略处理
if (SIG_ERR == ret)
{
perror("signal:");
exit(-1);
}
printf("before loop\n");
while(1)
{
printf("hello world!\n");
sleep(1);
}
printf("after loop\n");
return 0;
}
3.5.3.3、signal函数的优点和缺点
(1)优点:简单好用,捕获信号常用
(2)缺点:无法简单直接得知之前设置的对信号的处理方法
3.5.3.4、sigaction函数介绍
#include <signal.h>
int sigaction(int sig, const struct sigaction *act,struct sigaction *oact);
(1)2个都是API,但是sigaction比signal更具有可移植性
(2)用法关键是sigaction函数的两个指针参数
sigaction比signal好的一点:sigaction可以一次得到设置新捕获函数和获取旧的捕获函数(其实还可以单独设置新的捕获或者单独只获取旧的捕获函数),而signal函数不能单独获取旧的捕获函数而必须在设置新的捕获函数的同时才获取旧的捕获函数。
sigaction代码示例:
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
void func(int sig)
{
if(SIGINT!=sig)
return ;
printf("信号num是:%d\n",sig);
}
int main(void)
{
struct sigaction act;
act.sa_handler=func;
int res=-1;
res=sigaction(SIGINT,&act,NULL);
if(res==-1)
{
perror("sigaction");
_exit(-1);
}
printf("before loop:\n");
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
3.5.4.alarm和pause函数
3.5.4.1、alarm函数:主要是在设定秒数后向signal发送信号,然后执行绑定函数;alarm()函数用于在系统中设置一个定时器,在定时器到时后会向进程发送SIGALRM信号。SIGALRM信号的 默认处理方式是终止进程。
(1)内核以API形式提供的闹钟(内核帮一个进程只维护一个alarm时钟)
(2)编程实践
所需头文件
#include<unistd.h>
函数原型
unsigned int alarm(unsigned int seconds)
函数参数
seconds:指定秒数
函数返回值
成功:如果调用此alarm()前,进程已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
出错:-1
注意:alarm只设定一个闹钟,时间到达并执行其注册函数之后,闹钟便失效。【如果想循环设置闹钟,需在其注册函数(hander函数中)再调用alarm函数。】
代码示例:
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
void handler()
{
printf("Hello。。。。。\n");
}
void main()
{
int i;
signal(SIGALRM, handler); //让内核做好准备,一旦接受到SIGALARM信号,就执行 handler
alarm(5);//在5秒过后执行handler函数
for(i=1;i<21;i++)
{
printf("sleep %d ...\n",i);
sleep(1);
}
}
3.5.4.2、pause函数
(1)内核挂起
(2)代码实践
pause函数的作用就是让当前进程暂停运行,交出CPU给其他进程去执行。当当前进程进入pause状态后当前进程会表现为“卡住、阻塞住”,要退出pause状态当前进程需要被信号唤醒。
3.5.4.3、使用alarm和pause来模拟sleep
pause函数代码示例:
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void sigroutine(int unused)
{
printf("Catch a signal SIGINT \n");
}
int main()
{
signal(SIGINT, sigroutine);//SIGINT 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出
pause();//在没有收到任何的信号的时候,pause相当于一个死循环,当收到任何一个信号的时候,就执行下一句
sleep(3);
printf("receive a signal \n");
}
使用alarm和pause来模拟sleep代码示例:
#include <stdio.h>
#include <unistd.h> // unix standand
#include <signal.h>
void func(int sig)
{
/*
if (sig == SIGALRM)
{
printf("alarm happened.\n");
}
*/
}
void mysleep(unsigned int seconds);
int main(void)
{
printf("before mysleep.\n");
mysleep(3);
printf("after mysleep.\n");
/* unsigned int ret = -1;
struct sigaction act = {0};
act.sa_handler = func;
sigaction(SIGALRM, &act, NULL);
//signal(SIGALRM, func);
ret = alarm(5);
printf("1st, ret = %d.\n", ret);
sleep(3);
ret = alarm(5); // 返回值是2但是本次alarm会重新定5s
printf("2st, ret = %d.\n", ret);
sleep(1);
ret = alarm(5);
printf("3st, ret = %d.\n", ret);
//while (1);
pause();
*/
return 0;
}
void mysleep(unsigned int seconds)
{
struct sigaction act = {0};
act.sa_handler = func;
sigaction(SIGALRM, &act, NULL);
alarm(seconds);
pause();
}