信号的发送主要由函数kill、raise、sigqueue、alarm、setitimer以及abort来完成
kill函数
kill函数用来发送信号给指定的进程。
#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int sig);
该函数的行为与第一个参数pid有关,第二个参数sig表示信号编号。
- 如果pid是正数,则发送信号sig给进程号为pid的进程
- 如果pid为0,则发送信号sig给当前进程所属的进程组里的所有进程
- 如果pid为-1,则把信号sig广播至系统除1号进程(init进程)和自身以外的所有进程
- 如果pid是比-1还小的负数,则发送信号sig给属于进程组-pid(也就是pid的绝对值) 的所有进程
- 如果参数sig是0,则kill仍执行正常的错误检查,但不发送信号。可以利用这一点来确定某新城是否有权向另外一个进程发送信号。如果向一个并不存在的进程发送空信号,则kill返回-1,errno被设置为ESRCH
函数执行成功返回0,当有错误发生时返回-1,错误代码存入errno。
注意:只有具有root权限的进程才能向其他任意进程发送信号,非root权限的进程只能向属于同一个组或同一个用户的进程发送信号。
示例程序1
该示例程序实现了自己的kill命令。在shell中kill命令也是用来发送信号的,我们用代码仿照shell的kill命令,但是不支持-l选项(显示信号编号)
//本程序只支持按信号的编号发送信号
#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
#include<stdlib.h>
#include<string.h>
int main(int argc,char **argv){int i,j;int signum=SIGTERM;//默认发送SIGTERMpid_t pid;//首先检查参数,要么是只有一个参数,也就是pid,要么是三个参数,也就是 -s signum pidif(argc!=2&&argc!=4){printf("Usage:./my_kill <-s signum>[PID]\n");exit(0);}for(i=1;i<argc;i++){//如果有-s,找到-s后面的参数,它就是signumif(!strcmp(argv[i],"-s")){signum=atoi(argv[i+1]);break;}}if(argc==2){//如果只有一个参数,它就是pidpid=atoi(argv[1]);}else{for(j=1;j<argc;j++){//i是在-s,所以j=i+2的时候就指向的是pidif(j!=i&&j!=i+1){pid=atoi(argv[j]);break;}}}if(kill(pid,signum)<0){perror("kill");exit(1);}return 0;
}
运行的方式:./my_kill -s 2(这是signum) 2568(这是pid)
这个例子可以配合之前几节写的信号接收的程序使用来测试
raise函数
raise函数是ANSI C而非POSIX标准定义的,用来给调用它的进程发送信号。
#include<signal.h>
int raise(int sig);
示例程序2
这里用raise函数写一个自发自收的信号
#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
#include<stdlib.h>
#include<string.h>
void process(int signum){printf("recv!\n");
}
int main(int argc,char **argv){signal(SIGINT,process);raise(SIGINT);return 0;
}
这都很简单,就不解释了。
sigqueue函数
这个函数支持信号带有参数,从而可以与函数sigaction配合使用
#include<signal.h>
int sigqueue(pid_t pid,int sig,const union sigval value);
sigqueue的另一个与kill的不同点是它不能给一组进程发送信号
参数value是一个union共用体,定义如下
union sigval{int sival_int;void *sival_ptr;
};
union的特点就是只能是其中一个。也就是说信号携带的要么是一个整型值,要么是一个void型指针。当接收进程的信号处理函数是由sigaction设置的并且设置了SA_SIGINFO标准,接收进程可以从siginfo_t结构的si_value域取得信号发送时携带的参数
成功执行返回0,发生错误返回-1,错误码存到errno里。
程序3
这个函数书上竟然没有给示例,我尝试自己写一个demo来测试一下。
首先是接收方的程序,设置一下sigaction
#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
void receive_process(int signum,siginfo_t *info_ptr,void *unused_ptr){printf("recv!\n");printf("data is:%d\n",info_ptr->si_value.sival_int);
}
int main(int argc,char **argv){printf("my pid:%d\n",getpid());struct sigaction act;act.sa_flags=SA_SIGINFO;act.sa_sigaction=receive_process;sigaction(SIGINT,&act,nullptr);while(1);return 0;
}
然后是发送方的程序
#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
int main(int argc,char **argv){pid_t pid=atoi(argv[1]);int data=atoi(argv[2]);union sigval value;value.sival_int=data;sigqueue(pid,SIGINT,value);return 0;
}
运行结果如图
完美的发送和接收,证明讲的确实没问题。
我在上一节里写的si_value的类型是sigval_t,我去源码里翻了下, sigval_t和union sigval是一样的,只是重新命名了。
sigaction不会写的可以看我之前写的文章《Linux C编程实战》笔记:信号的捕捉和处理-CSDN博客
alarm函数
alarm函数可以用来设置定时器,定时器超时将产生SIGALRM信号给调用进程。
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
经过seconds秒后,内核将给调用该函数的进程发送SIGALRM信号,如果seconds为0,则不再发送SIGALRM信号。最新一次调用alarm函数将取消之前一次的设定。
注意:alarm只设定为发送一次信号,如果要多次发送,就要对alarm进行多次调用。
示例程序4
该示例程序模拟网络命令ping的功能。
其实我也不知道ping是干啥的...反正跟着敲吧。
#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
void send_ip(){printf("send a icmp echo request packet\n");
}
void recv_ip(){while(1);
}
void handler_sigalarm(int signo){send_ip();alarm(2);
}
int main(int argc,char **argv){signal(SIGALRM,handler_sigalarm);raise(SIGALRM);//触发一个SIGALRM信号给本进程recv_ip();return 0;
}
其实就是一直在发这段话罢了。整个程序也很好懂,就不多讲了。
getitimer/setitimer函数
与alarm函数一样,setitimer函数也是用来设置定时器的,且alarm和setitimer使用的是同一个定时器,因此会相互影响。setitimer要比alarm具有更多的功能。
#include<sys/time.h>
int getitimer(int which,struct itimerval *value);
int setitimer(int which,const struct itimerval *value,struct itimerval *ovalue);
第一个参数which用来指定使用哪一个定时器,根据参数which可单独设定每个定时器,定时器的种类如下:
参数value用来指定定时器的时间,结构struct itimerval的定义如下:
struct itimerval{struct timeval it_interval;struct timeval it_value;
}
//解释一下setitimer里这个结构体的使用,首先定时器时间是it_value,这个计完后会发一个信号,之后
//it_value会重新变为it_interval,并且之后一直以it_interval为间隔循环计时,计到0也会发送信号
struct timeval{long tv_sec;//秒数long tv_usec;//微秒
}
对于函数getitimer,如果存在由which指定的定时器,则将剩余时间保存在it_value中,该定时器的初始值保存在it_interval中;如果不存在指定类型的定时器,则将value置为0返回。执行成功返回0,当有错误发生时则返回-1,错误代码存入errno中。
示例程序5
#include<stdio.h>
#include<sys/time.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
//信号处理程序
void handler_sigtimer(int signo){switch (signo){case SIGALRM:printf("recv SIGALRM\n");break;case SIGPROF:printf("recv SIGPROF\n");break;default:break;}
}
int main(int argc,char **argv){struct itimerval value;//安装信号处理函数signal(SIGALRM,handler_sigtimer);signal(SIGPROF,handler_sigtimer);//初始化value结构value.it_value.tv_sec=1; //第一次1秒触发信号value.it_value.tv_usec=0;value.it_interval.tv_sec=5; //第二次之后都是5秒触发信号value.it_interval.tv_usec=0;//设置定时器setitimer(ITIMER_REAL,&value,nullptr);setitimer(ITIMER_PROF,&value,nullptr);while(1);return 0;
}
程序设置了两个定时器ITIMER_REAL和ITIMER_PROF。系统经过1秒后,将触发一个SIGALRM信号,以后每5秒触发一个SIGALRM信号。按照程序执行时消耗的时间以及内核因本程序消耗的时间来计时。第一次经过一秒后将触发一个SIGPROF信号,以后每5秒触发一个SIGPROF信号。
abort函数
#include<stdlib.h>
void abort(void);
如果进程设置了信号处理函数以捕获SIGABRT信号,且信号处理函数不返回(如使用longjmp),则abort()不能终止进程。abort()终止进程时,所有打开的流(如i/o流、文件流)均会被刷新和关闭。如果进程设置了SIGABRT被阻塞或忽略,abort()将覆盖这种设置。