说起信号,就像是一个软中断一样,就是一个信号来了以后我们程序中断当前执行的代码,找到之前注册过的相应信号的执行代码进行处理.其实我们使用的非常的广泛了,就像我们在终端里面要停止一个进程的运行,我们会同时按下ctrl+C的按键来终止程序,这个就是一个信号,是停止信号,是标号为9的信号,我们可以使用kill -l的命令来查看我们系统支持的信号.很多信号的处理都有系统默认的方式进行解决,所以我们在编程中并没有对9号信号进行处理,我们的程序却会在ctrl+C的信号到来的时候终止程序.一般Linux系统中支持的信号有64种,这里就不介绍了.我们可以在编程的时候使用自己的方式去对信号进行处理而不调用系统默认的处理方法.
我们要处理信号首先要进行信号处理函数的注册.
#include <signal.h>
void (*signal(int signum,void (*handler)(int)));
第一个参数是信号的编号,第二个参数是一个函数指针.指向的是信号处理函数,还可以为SIG_ING 或略参数指定的信号.
既然是进程之间的通信,所以就会有发送信号的进程,使用函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int sig);//pid 指定信号的接受者的进程号,sig 发送信号的编号
pid > 0 是将信号发送给该进程
pid = 0是将信号发送给和自己进程相同的进程组中的进程
pid = -1将信号广播给所有进程
pid < 0将信号发送给进程识别码为pid的绝对值的所有进程
因为管道的传输数据的方式容易失败和阻塞和程序不方便处理其他事情,我通常使用信号配合管道使用,当某一程序写完管道以后,向指定的程序发送信号,这样指定的接受到信号以后读取管道中的内容,这样可以及时的读取管道的内容,而且不会堵塞,程序还可以干其他的事情.
下面是信号处理的代码:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <signal.h> 4 #include <unistd.h> 5 #include <errno.h> 6 #include <fcntl.h> 7 8 #define FIFO_PATH "/home/cy/myfifo" 9 10 int Rfifo = 0; 11 12 void mySig(int sig_num){//信号处理函数 13 14 char buf[1024] = {0}; 15 16 printf("信号来了\n"); 17 18 read(Rfifo,buf,1024);//读取管道内容 19 printf("%s\n",buf); 20 21 } 22 23 int main() 24 { 25 if(mkfifo(FIFO_PATH,0666) < 0 && errno !=EEXIST){//创建管道 26 perror("创建错误\n"); 27 return -1; 28 } 29 Rfifo = open(FIFO_PATH,O_CREAT | O_RDONLY,0666); 30 if(Rfifo < 0){ 31 perror("wrong\n"); 32 return -1; 33 } 34 35 36 while(1){ 37 signal(10,mySig);//注册10号信号的处理函数 38 pause();//因为没有其他需要执行的工作,使用该函数将该进程休眠等待信号的到来 39 printf("sleeping\n"); 40 } 41 42 close(Rfifo); 43 return 0; 44 }
在向指定进程发送信号之前需要获取该进程的Pid,我们现在只知道对方进程的名称,而每次程序执行的时候Pid都是不一样的,这样我们就需要通过进程的名称获取到Pid的值,shell命令pidof 进程名,返回的结果就是该名称进程的Pid,我们可以在程序代码中执行这个命令来获取指定进程的Pid.
下面是向指定进程发送信号的代码:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <signal.h> 4 #include <unistd.h> 5 #include <errno.h> 6 #include <fcntl.h> 7 8 #define FIFO_PATH "/home/cy/myfifo" 9 10 int main(int argc,char *argv[]) 11 { 12 pid_t pid = 0; 13 FILE *fpid = NULL; 14 char name[] = "pidof sig1";//shell命令,获取指定名称的函数的pid号. 15 char needPid[6] = {0}; 16 int Wfifo = 0; 17 18 fpid = popen(name,"r");//执行获取pid号的命令并返回文件描述符 19 20 fgets(needPid,6,fpid);//根据文件描述符读取命令的返回结果 21 pid = atoi(needPid);//将字符型的结果转化为int型的pid 22 23 pclose(fpid);//关闭该文件描述符 24 25 if(mkfifo(FIFO_PATH,0666) < 0 && errno != EEXIST){ 26 perror("创建错误\n"); 27 return -2; 28 } 29 Wfifo = open(FIFO_PATH,O_CREAT | O_WRONLY,0666); 30 if(Wfifo < 0){ 31 perror("wrong\n"); 32 return -1; 33 } 34 35 while(1){ 36 37 printf("while\n"); 38 write(Wfifo,name,15);//向管道中写信息 39 kill(pid,10);//向指定的进程发送指定的信号 40 sleep(2);//休眠两秒 41 printf("sending sig\n"); 42 43 }
在使用自己处理信号的时候,如果不是需要修改系统预定义的信号的处理方式,最好不要使用系统已经定义好了的信号,可以使用系统预留给用户自定义的信号如10号12号等.还有一些信号是没法忽略的如SIGKILL和SIGSTOP不能忽略也不能被修改默认的执行方式.
还有用的比较多的是闹钟信号14号
#include <unistd.h>
unsignal int alarm(unsignal int seconds);
这个函数是向自己发送定时信号,默认的动作是退出程序,我们也可以自定义动作,执行周期性的操作.
int raise(int sig);//这个函数是向自己发送信号,个人感觉这个函数无关紧要
-------------------------------------------------------------------------------------------------
在实际的应用中一个应用程序需要对多个信号进行处理,为了方便,linux系统引进了信号集的概念。信号集用多个信号组成的数据类型sigset_t.可用以下的系统调用设置信号集中所包含的数据。
#include <signal.h>
int sigemptyset(sigset_t *set);//用作初始化set指向的信号集, 清空其中的所有信号.
成功则返回0, 出错则返回-1.
#include <signal.h>
int sigfillset(sigset_t *set);//初始化set指向的信号集, 填充其中的所有信号.
成功则返回0, 出错则返回-1.
#include <signal.h>
//向set指向的信号集中, 增加/删除一个signo代表的信号.
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
成功则返回0, 出错则返回-1.
#include <signal.h>
int sigismember(const sigset_t *set, int signo);//判断signo信号是否在set指向的信号集中.
真则返回1, 假则返回0, 出错则返回-1.