进程通信——信号
一.改写Ctl+C的信号处理方式
1.代码:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void ouch(int sig){
printf("OUCH! I got the signal %d\n",sig);
(void)signal(SIGINT,SIG_DFL); //恢复信号初始设定
}
int main(void){
signal(SIGINT,ouch); //改变信号处理方式
while(1){
printf("Hello world\n");
sleep(1);
}
}
//1.#include<signal.h>
//(void)signal(SIGINT,SIG_DEF)
//2.SIGINT:键盘中断 SIG_DFL:恢复信号初始处理方式
//3.注意(void)signal(SIGINT,ouch) 不同位置
运行结果如图:
2)发送信号:通过ctr+c发送终止信号,试试键盘其他信号,看看是否有效
无效,SIGINT为输入中断信号
3)预置信号的处理方式:通过signal设置信号的处理方式。消去注释1或者2,看程序变化
1.仅消去注释1:作用:该注释为恢复信号的默认处理方式 ;结果:则对中断输入信号操作仅printf(),
2.仅消去注释2:作用:设置遇到输入中断信号的处理方式为ouch(); 结果:一个ctr+c结束
4)接受信号的进程按照设定完成指定的事件处理:ouch(int sig)安排接到信号后的处理方式
1.(void)signal(SIGINT,SIGDFL) 恢复系统对输入中断信号的默认处理方式
二.闹钟,通过系统定时发送alarm()定时发送信号
1.代码:
#include<stdio.h>
#include<unistd.h> //alarm()
#include<signal.h> //signal()
void handler(){
printf("hello\n");
}
int main(void){
int i;
signal(SIGALRM,handler);
alarm(3);
for(i=0;i<6;i++){
printf("sleep is %d\n",i);
sleep(1);
}
return 0;
}
//1.SIGALRM:定时发送信号 alarm(n):具体定是发送的时间,
//2.alarm:多alarm只发送一次,且只发送最大时间间隔
运行结果如图:
2)观察输出结果,理解程序用alarm定时发送信号
alarm()
1.头文件:#include<unistd.h>
2.作用:启动后,计时到了,发送信号给进程
3.函数原型:unsigned int alarm(unsigned int seconds);
4.函数参数:unsigned int seconds:指定秒数
5.返回值:成功:0(但是如果之前还设置其他alarm(),则返回上一个闹钟的剩余时间)
失败:-1
6.注意:一个进程只能由一个alarm(),默认最长时间的
7.使用:
#include<unistd.h>
{
signal(SIGALRM,handler);
alarm(3);
}
三.用setitimer设置定时器,getitimer获取定时器信息
1)代码:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<sys/time.h>
int sec;
void sigroutine(int signo){
switch(signo){
case SIGALRM:printf("I got a signal-----SIGALRM\n");break;
case SIGVTALRM:printf("I got a signal --------SIGVTALRM\n");break;
}
return ;
}
int main(){
struct itimerval value,ovalue,value2; //
sec=5;
printf("the process id is %d\n",getpid());
signal(SIGALRM,sigroutine);
signal(SIGVTALRM,sigroutine);
value.it_value.tv_sec=1;
value.it_value.tv_usec=0;
value.it_interval.tv_sec=0;
value.it_interval.tv_usec=500000;
setitimer(ITIMER_REAL,&value,&ovalue); //
value2.it_value.tv_sec=0;
value2.it_value.tv_usec=250000;
value2.it_interval.tv_sec=0;
value2.it_interval.tv_usec=250000;
setitimer(ITIMER_VIRTUAL,&value2,&ovalue);
for(;;);
return 0;
}
//1.#include<sys/time.h>
// struct itimerval
// setitimer(,,) (百度百科)
// ITIMER_REAL:按照实际时间计时,计时到了,发送SIGLARM给进程
// ITIMER_VIRTUAL:按照进程执行的时间计时,计时到了,发送SIGVTALRM给进程
// value.it_value.tv_sec=1:计时器初始启动时间
// value.it_interval.tv_usec=0:计时器启动后的时间间隔
运行结果如图:
2)了解sys/tims.h关于struct itimerval的定义
1.
struct itimerval{
struct timeval it_interval; //interval:间隔
struct timeval it_value; //value:启动值
}
struct timeval{
long tv_sec; //second,秒
long tv_usec; //usecond,微秒 1秒=1000000微秒
}
3)了解定时器ITIMER_REAL和ITIMER_VITUAL的差异,明白实际时间和进程执行时间的差异
1.
ITIMER_REAL:按照实际时间计时,计时到了,发送SIGLARM给进程
ITIMER_VIRTUAL:按照进程执行的时间计时,计时到了,发送SIGVTALRM给进程
4)setitimer()函数
1.头文件:#include<sys/times.h>
2.函数作用:设置定时器
3.函数原型:int setitimer(int which,const struct itimerval *value,const struct itimerval *ovalue)
4.函数参数:
a.int which:
三种定时器类型:
ITIMER_REAL:按照实际时间计时,计时到了发送SIGALRM信号给进程
ITIMER_VIRTUAL:按照进程执行时间计时,计时到了发送SIGVTALRM信号给进程
ITIMER_PROF:按照进程执行和系统为该进程调用执行时间计时,计时到了发送SIGPROF信号给进程
b.struct itimerval{
struct timerval it_interval; //interval:间隔
struct timerval it_value; //it:interval timer value
} //tv:time value
struct timerval{
long tv_sec;
long tv_usec; //微秒:1秒=1000 000微秒
}
5.返回值:成功:0
失败:-1
6.使用:
#include<sys/time.h>
struct itimerval value,ovalue;
{
value.it_value.tv_sec=1;
value.it_value.tv_usec=0;
value.it_interval.tv_sec=0;
value.it_interval.tv_usec=5000 00;
setitimer(ITIMER_REAL,&value,&ovalue);
}
四.父子进程间通过kill发送信号
1)代码
#include<stdio.h> //printf()
#include<unistd.h> //fork(),pause()
#include<stdlib.h> //exit()
#include<signal.h> //signal(),kill()
static int alarm_fired=0;
void ding(int sig){
alarm_fired=1;
}
int main(void){
int pid;
printf("Alarm application starting\n");
if((pid=fork())==0){
sleep(5);
kill(getppid(),SIGALRM);
exit(0);
}
printf("waiting for alarm go off\n");
(void) signal(SIGALRM,ding);
pause();
// wait(0);
if(alarm_fired)
printf("DING\n");
printf("done\n");
}
//1.kill(getppid(),SIGALRM)
// pause():头文件:#include<unistd.h> 释义:暂停 作用:休眠当前进程,直到信号到达
运行结果如图:
2)kill()的用法:
1.头文件:#include<signal.h>
2.作用:给指定进程发送指定信号
3.函数原型:void kill(pid_t pid,int sig)
4.函数参数:
1.pid_t pid:
a.当pid>0,发送给该进程号进程信号
b.当pid=0,发送给该进程组信号
c.当pid=-1,发送给除1和本身外所有进程信号
d.当pid<-1,发送给进程号为-pid的进程信号
2.sig:
a.1~31信号为不实时信号,34~64为实时信号(可靠信号)
5.返回值:成功:0
失败:-1
6.使用:
#include<signal.h>
{
(void) signal(SIGALRM,ding);
kill(getpid(),SIGALRM);
}
signal()的用法:
1.头文件:#include<signal.h>
2.作用:设置信号处理方式
3.函数原型:void(*signal(int signum,void(* handler)(int)))(int)
4.函数参数:
a.signum:信号编号
b.handler:
1.handler:函数,则按照函数执行
2.SIG_ING:忽略该信号执行
3.SIG_DFL:恢复系统信号初始设定处理方式
5.返回值:成功:0
失败:-1
6.使用:
#include<signal>
{
(void*)signal(SIGINT,ding);
(void*)signal(SIGINT,SIGDFL);
}
3)掌握父子进程获取对方进程号的方法:
1.子获取父的进程号: getppid()
2.父进程获得子进程信号方法:fork()的父进程返回值
4)取消语句1,观察输出变化,并解释
1.pause是暂停父进程。去掉pause(),父进程执行,重设置了SIGALRM信号处理方式。但是因为信号是在子进程中发出的,父进程还未接受到信号就终止。子进程再向父进程发送信号时,父进程已经终止了。
5)取消语句1,保留语句2,观察输出变化,并解释原因
1.wait(0)是等待子进程完成,父进程重设SIGALRM信号的处理方式,wait(0),父进程等待子进程结束。子进程被调用执行,发送信号SIGALRM给父进程,父进程处理信号
五.使用信号进行进程间的通信
1.代码:
进程使用用 fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上来的中断信号(即 按CTRL+c 键);
捕捉到中断信号后,父进程用系统调用kill()向两个子进程发出信号,进程捕捉到信号后分别输出提示信息后终止;父进程等待两个子进程终止后,输出提示信息后终止。
#include<stdio.h>
#include<stdlib.h> //exit()
#include<unistd.h> //fork(),sleep()
#include<signal.h> //kill(),signal()
#include<sys/types.h>
#include<sys/wait.h> //wait()
//#include<semaphore.h>
int wait_mark;
//sem_t sem;
void waiting(){
while(wait_mark!=0);
}
void stop(){
wait_mark=0;
}
int main(void){
int p1,p2;
// sem_init(&sem,0,1);
signal(SIGINT,stop);
while((p1=fork())==-1);
if(p1>0){
while((p2=fork())==-1);
if(p2>0){
//主进程的处理
wait_mark=1;
waiting();
kill(p1,16);//
kill(p2,17);//
wait(0);
wait(0); //同步
printf("parents is killed\n");
exit(0);
}
else{
//p2进程的处理
wait_mark=1;
signal(17,stop);
waiting(); //等待信号17
// sem_wait(&sem);
printf("p2 is killed by Parent1\n");
fflush(stdout);
sleep(1);
printf("p2 is killed by Parent2\n");
fflush(stdout);
// sem_post(&sem);
exit(0);
}
}
else{
//p1进程的处理
wait_mark=1;
signal(16,stop);
waiting(); //等待信号16
// sem_wait(&sem);
printf("p1 is killed by parent1\n");
fflush(stdout);
sleep(1);
printf("p1 is killed by parent2\n");
fflush(stdout);
// sem_post(&sem);
exit(0);
}
}
//1.fork():父进程中返回子进程ID
注意:运行后要CTR+C,发送信号后才会显示结果
运行结果如图:
2.了解父进程利用fork的返回值,向子进程发送信号
1.fork()函数,在父进程中返回子进程的ID,调用kill函数,向子进程发送信号
3.观察下图输出,使用互斥控制,使得输出结果不交替
去掉注释
六.使用sigaction注册信号
1.代码
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
void new_op(int signum,siginfo_t* info,void *myact){ //
printf("receive the signum %d\n",signum);
sleep(1);
}
int main(int argc,char **argv){
int sig;
struct sigaction act;
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask); //清空信号集
act.sa_flags=SA_SIGINFO; //设置信号处理其他相关操作
act.sa_sigaction=new_op; //信号处理函数
if(sigaction(sig,&act,NULL)<0) //*
printf("install signal error\n");
while(1){
sleep(3);
printf("wait for the signal\n");
}
}
注意:按照2)说明进行
2)说明,命令行参数信号值;启动另一终端,运行ps -a可以获得其进程ID;运行 kill -s signo pid检验信号的发送接收及处理
运行结果如图:
3)了解sigaction的结构定义
strcut sigaction{
void (*sa_handler)(int);
void (*sa_sigaction)(int,siginfo_t*,void*);
sigset_t mask;
int sa_flags;
void (*sa_restore)(void)
}
4)了解使用sigaction函数注册新信号
1.头文件:#include<signal.h>
2.作用:查询设置信号处理方式
3.函数原型:int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact)
4.函数参数:
1.int signum:依据signum指定的信号编号设置信号的处理函数
2.strcut sigaction{
void (*sa_handler)(int); //处理函数
void (*sa_sigaction)(int,siginfo_t*,void*);
sigset_t mask; //暂时将mask所指的信号集搁置
int sa_flags; //设置信号处理的其他相关操作
void (*sa_restore)(void)
}
5.返回值:成功:0
失败:-1
6.使用:
#include<signal.h>
void new_op(int signum,siginfo_t* info,void *myact){ //
}
int main(int argc,char **argv){
int sig=atoi(argv[1]);;
struct sigaction act;
sigemptyset(&act.sa_mask); //清空信号集
act.sa_flags=SA_SIGINFO; //设置信号处理其他相关操作
act.sa_sigaction=new_op; //信号处理函数
sigaction(sig,&act,NULL) //*
}
5)理解信号注册,信号发送,信号处理三个环节
七.使用sigqueue发送信号并传递附加信息
1.代码:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<signal.h>
void new_op(int signum,siginfo_t *info,void *myact){
int i;
for(i=0;i<10;i++){
printf("%c ",(*((char*)((*info).si_ptr)+i))); //*
printf("\nhandle signal %d over\n\n",signum);
}
}
int main(int argc,char **argv){
int i;
int sig;
pid_t pid;
struct sigaction act;
union sigval mysigval; //
char data[10];
memset(data,0,sizeof(data)); //初始化函数,头文件string.h
for(i=0;i<10;i++)
data[i]='$';
mysigval.sival_ptr=data; //*
sig=atoi(argv[1]);
pid=getpid();
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO; //信息传递开关,允许传送参数信息给new_op
act.sa_sigaction=new_op; //三参数信号处理函数
if(sigaction(sig,&act,NULL)<0)
printf("install the signal error\n");
while(1){
sleep(2);
printf("wait for the signal\n");
sigqueue(pid,sig,mysigval); //向本进程发送信号,并传递附加信息
}
}
运行结果如图:
2)了解union sigval的定义
union sigval{
int sival_int;
void *sival_ptr; //指向要传递的信号参数
}
3)了解sigqueue函数发送信号
1.头文件:#include<signal.h>
2.函数作用:在队列中向指定进程发送信号和数据
3.函数原型:int sigqueue(pid_t pid,int sig,const union sigval value)
4.函数参数:
1.pid_t pid:进程号
2.int sig:发送的信号
3.const union sigval value:信号附带的顺序
union sigval{
int sival_int;
void *sival_ptr; //指向要传递的信号参数
}
5.返回值:成功:0
失败:-1
6.函数使用:
#include<signal.h>
{
union struct sigval mysigval;
mysigval.sival_ptr=data;
sigqueue(pid,sig,mysigval);
}
4)观察进程给自己发送信号
1.sigqueue(pid,sig,mysigval)中,pid=getpid(),自己的进程
八.修改前面的程序,将信号发送和接收放在两个程序,并在发送过程中传递整型参数
1.代码:
//信号接收程序:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
void new_op(int signum,siginfo_t *info,void *myact){
printf("the int value is %d\n",info->si_int);
}
int main(int argc,char **argv){
int sig;
pid_t pid;
struct sigaction act;
sig=atoi(argv[1]);
pid=getpid();
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=new_op;
if(sigaction(sig,&act,NULL)<0)
printf("install the signal error\n");
while(1){
sleep(3);
printf("wait for the signal\n");
}
}
//信号发送程序:
#include<stdio.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/types.h>
#include<signal.h>
int main(int argc,char **argv){
int signum;
pid_t pid;
union sigval mysigval; //
signum=atoi(argv[1]);
pid=(pid_t)atoi(argv[2]);
mysigval.sival_int =8;
if(sigqueue(pid,signum,mysigval)==-1)
printf("send error\n");
sleep(1);
return 0;
}
注意:
1.先开一个终端,先运行信号接收程序: ./t 35
2.再开一个终端,先查看进程:ps -a
./p 35 进程号
3.具体如下图:
运行如图:
2)理解发送进程和接收进程
3)按照程序要求,在运行时附加合适的参数,并运行检验
八.编写程序
1.用 fork()创建一个子进程,在子进程中再次调用fork()创建孙子进程。调用系统调用 signal(),让父进程捕捉键盘上来的中断信号(即按^c 键);捕捉到中断信号后,父进程用系统调用kill(O向子进程发出信号,子进程捕捉到信号后,调用kill()向孙子进程发出信号
孙子进程捕捉到信号后,输出下列信息后终止:
Grandson processl is killed by son!
子进程等待孙子进程终止后,输出如下的信息后终止:
Child process is killed by parent!
父进程等待子进程终止后,输出如下的信息后终止:
Parent process is killed!
提示:过程如下所示。
父进程 子进程 孙进程
捕捉键盘信号: Kill() 接收信号:
#include<stdio.h>
#include<stdlib.h> //exit()
#include<unistd.h> //fork(),sleep()
#include<signal.h> //kill(),signal()
#include<sys/types.h>
#include<sys/wait.h> //wait()
int wait_mark;
void waiting(){
while(wait_mark!=0);
}
void stop(){
wait_mark=0;
}
int main(void){
int p1,p2;
signal(SIGINT,stop);
while((p1=fork())==-1);
if(p1>0){
while((p2=fork())==-1);
if(p2>0){
//主进程的处理
wait_mark=1;
waiting();
kill(p1,16);//
kill(p2,17);//
wait(0);
wait(0); //同步
printf("parent process is killed\n");
exit(0);
}
else{
//p2进程的处理
wait_mark=1;
signal(17,stop);
waiting(); //等待信号17
printf("Grandson process is killed by son\n");
fflush(stdout);
exit(0);
}
}
else{
//p1进程的处理
wait_mark=1;
signal(16,stop);
waiting(); //等待信号16
printf("child process is killed by parent\n");
exit(0);
}
}
//1.fork():父进程中返回子进程ID
注意:运行后ctl+c
运行如图:
2.父子进程间互相发送信号:父进程无限循环向子进程发送信号,子进程收到信号
向父进程发送相同信号,父子进程之间各自记录全局变量的变化,结果如:
child caught signal #1
parent caught signal #1
child caught signal #2
parent caught signal #2
child caught signal #3
parent caught signal #3
…
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
int i;
void p_handle(){
printf("parent caught the signal # %d\n",i);
}
void c_handle(){
printf("child caught the signal # %d \n",i);
}
int main(void){
int p1;
if((p1=fork())>0){
(void)signal(17,p_handle);
for(i=0;i<10;i++){
pause();
kill(p1,16);
}
exit(0);
}
else{
(void)signal(16,c_handle);
for(i=0;i<10;i++){
kill(getppid(),17);
pause();
}
exit(0);
}
return 0;
}
//1.先signal,再kill:先改变信号的处理方式,再发送信号
//2.pause():阻塞直到信号到达
运行如图:
3.将编程题1用sigaction()和sigqueue()再实现一次,并在信号传递时附带上
息。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<signal.h>
void op(int signum,siginfo_t *info,void *myact){
printf("%s\n",(*info).si_ptr);
}
int main(int argc,char **argv){
int sig=atoi(argv[1]);
pid_t p1,p2;
struct sigaction act;
union sigval mysigval_parent,mysigval_son,mysigval_grandson; //
char data_parent[40]="parent process is killed";
mysigval_parent.sival_ptr=data_parent;
char data_son[40]="child process is killed by parent";
mysigval_son.sival_ptr=data_son;
char data_grandson[50]="Grandson process is killed by son";
mysigval_grandson.sival_ptr=data_grandson;
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO; //信息传递开关,允许传送参数信息给new_op
act.sa_sigaction=op; //三参数信号处理函数
if(sigaction(sig,&act,NULL)<0)
printf("install the signal error\n");
if((p1=fork())==0){
if((p2=fork())==0){
sigqueue(getpid(),sig,mysigval_grandson); //孙子
exit(0);
}
else{
wait(0);
sigqueue(getpid(),sig,mysigval_son); //儿子
exit(0);
}
}
else{
wait(0);
wait(0);
sigqueue(getpid(),sig,mysigval_parent); //父亲
exit(0);
}
}
//1.sigaction与sigqueue间的信息怎么连接?
//2.虚拟机在运行程序时为什么自动关闭虚拟机?
//注意:
//1.不要使用getppid(),会直接终端虚拟机运行 :直接用getpid()
//2.wait()在sigqueue()前使用
运行如图:
心得:1.在原程序的基础上进行渐变式的改变,以校验想法和错误修正。不要过大改变,会导致错误归因过多,无从下手