通往牛逼的道路上不是坎坷和荆棘,而是蹲着一大堆傻逼,你就可能是最大的一个。
----------匿名
先谈谈什么信号,信号就是进程正在执行,接受到信号,先执行信号,然后在继续执行进程,也是一种进程间通讯吧,就相当于JAVA中抛出异常。也是常说的中断中的软件中断,当然安全起见,不是所有的进程都可以接受任何信号,也不是所有的所有的进程都可以发送信号,要是一个恶意程序发一个SIGINT,所有的进程都终止了,这是个很严重的问题。 你进程中有多少信号可以用kill -l来查看,这是一部分,可以看出也是要注意的信号编号是从1开始不是从0;后面的编程会用到。想看每个信号的意义可以man一下。
一:如何产生和处理信号?我们在shell中终止进程会用ctrl + C 这是一种属于按下终端键,还有硬件产生例如被0除,主要将给介绍的是kill传递,用kill结束进程只是信号传递的一种而已。
操作系统课都讲过我就不在这里赘述了。这里还有介绍的一种状态就是挂起状态,挂起是属于就绪状态但人为的想让他阻塞,把他从就需队列放到阻塞队列,用到的sleep函数就是这个原理,可以用pause();让他挂起,其他状态都是收到信号后,等到开始运行先处理信号在执行程序,挂起是收到信号就唤醒进程,开始执行信号。
二: 有三种处理信号的方式:1忽略信号,2是自己注册个信号处理函数,扑捉到时就用自己的处理函数。3是执行系统默认,因信号的不同而采用终止进程或忽略。
理论总是那么苍白,只有代码才有说服力,以后代码实现不了的理论就不写,凡贴出代码的,一定会贴出运行结果。
发送信号函数 :头文件 :#include<signal.h>
函数原型: int kill(pid_t pid, int signo);
返回值: 成功返回0;失败返回-1;
kill可以向进程或进程组发送信号; pid > 0: 发送给进程给pid的进程(常用);pid == -1:发送给系统所有进程; pid == 0: 发送给进程组ID和该进程相同的进程;
pid < 0: 发送给进程组内ID 为pid的进程;
进程间发送需要注意2点:进程要有向发送信号的进程的权限;信号当然不可以随便发.那样你就可以随便结束其他进程了;2是系统进程不能接受信号;理由一样:
根用户可以向系统内任意进程发送信号。得到根用户权限的一样,可以终止任何进程。
也有向进程本身发送信号的函数:你可以用kill(getpid(), signo);有专用的函数 int raise(int signo);使用和kill 一样。
在这里唠叨两句其他的问题:你可以结束自己用raise(SIGKILL);这样你就结束了自己的进程但是没有做任何善后处理,拉完屎不檫屁股,不提裤子(例如关闭文件流,不把缓冲区的内容写入外存等),相当于_exit()函数,_exit和exit()函数不同,exit()会做善后处理工作。还有就是exit(0)是正常退出,exit(其他);是异常退出。
有的写成exit(EXIT_SUCCESS)和exit(EXIT_FAILURE);
设置信号处理函数:头文件#include<signal.h>
函数原型: void (*signal (int signo , void(* func) (int ) )) (int ); 别看这么麻烦,其实最简单,一会看例子。
返回值:返回值是一个指向函数的指针,这个函数指向上一次信号处理函数。
第二个参数是函数指针:有三个值:①SIG_IGN: 表示忽略该信号。注意的是SIGKILL 和 SIGSTOP不能忽略,要是能忽略,谁来终止他停止。②SIG_DEL表示默认的。③就是自己定义的函数指针。
再说2点就是进程不能创建信号,系统给用户两个信号用于通讯,SIGUSR1和SIGUSR2;
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<signal.h> 4 5 //自己定义的信号处理函数 6 void handler(int sigo) 7 { 8 switch(sigo) 9 { 10 case SIGUSR1: 11 printf("parent :catch sigusr1 \n"); 12 break; 13 case SIGUSR2: 14 printf("child ;catch sigusr2 \n"); 15 break; 16 default: 17 printf("should't here \n"); 18 break; 19 } 20 return ; 21 } 22 23 int main(void) 24 { 25 pid_t ppid,cpid; 26 27 //错误将返回SIG_ERR信号 28 if (signal(SIGUSR1,handler) == SIG_ERR) 29 { 30 perror("can't set handler for siguer1 \n"); 31 exit(1); //这是异常退出 32 } 33 34 if(signal(SIGUSR2,handler) == SIG_ERR) 35 { 36 perror("can't set handler for siguer2 \n"); 37 exit(1); 38 } 39 40 ppid = getpid(); 41 42 //创建子进程 43 if( (cpid = fork()) < 0) 44 { 45 perror("fail to fork \n"); 46 exit(1); 47 } 48 else if (cpid == 0) 49 {
printf("我是孩子 \n"); 50 //向父进程发送信号 51 if (kill(ppid, SIGUSR1) == -1) 52 { 53 perror("fail to send signal"); 54 exit(1); 55 } 56 57 while(1) ; 58 } 59 else 60 {63 //向子进程发送信号 64 65 if (kill(cpid, SIGUSR2) == -1) 66 { 67 perror(" fail to send signal"); 68 exit(1); 69 } 70 71 printf("kill child \n"); 72 //终止子进程的死循环 73 if (kill(cpid, SIGKILL) == -1) 74 { 75 perror ("fail to send wair \n"); 76 printf("孩子为啥你一直收不到啊"); 77 exit(1); 78 } 79 //防止产生僵尸进程 80 if(wait(NULL) == -1) 81 { 82 perror("fail to wait \n"); 83 exit(1); 84 } 85 } 86 87 return 0; 88 89 }
这个程序调试了3个多小时,最后确定语法没问题,这个例子也可以说出上面那两个函数的用法,纠结的地方时子进程先运行还是父进程先运行,他们是异步的,他们是一个先运行,紧接着另一个也开始运行,并不是一个运行完了另一个才运行,还有就是父进程给子进程传信号,后面的就是终止子进程的信号,所以子进程老是收不到,因为他已经被kill了,在给子进程发送SIGKILL信号时前sleep(2);就差不多,这个是截图没用shell,用的集成环境,在shell中调试有点麻烦,在windows下用不成,出错信息是没有信号量,调试这个程序有种高中做证明题的感觉。最后就是在调错时试图给SIGKILL做个处理函数就是给他也handler一下,结果出错,这是当然的SIGKILL 和SIGSTOP 是不可屏蔽的。
三:谈谈定时器吧就是alarm闹钟。老规矩:
函数原型:#include<unisd.h>
函数原型: unsigned int alarm(unsigned int seconeds);
返回值: 定时器不存在。或已超时,返回0;定时器还没超时,返回剩余的秒数。
alarm参数是0时可以取消一个定时器。
还有要说的就是alarm确是不是不是一个精确的,因为他最小的单位是秒,对于计算机执行一条指令所要花费的时间你懂得。
还有他可以用于定时等待I/o外设不可用时,超过了某一时间就不等了,例如网络的设备中数据包未到达。你会问前面的还有现在举了好多例子都是网络,端口,也没好多个也就几个,因为这些都是为了我们的终极目的网络编程打基础。
好了就算是这么函数也举个例子把。勿以函数小而不为之,
#include<stdio.h> #include<signal.h> #include<stdlib.h> void time_handler(int signo) { if (signo == SIGALRM) { printf("the time is : \n"); exit(0);//退出程序了;不会返回到扑捉信号的那个函数了 } else printf("unexpecd signal \n"); } int main() { if (signal(SIGALRM,time_handler) == SIG_ERR) { perror("fail to signal \n"); exit(1); } alarm(1); while(1) printf("hello word \n"); printf("上面是个死循环,不会到这 \n"); return 0; }
1S 就运行了这么多,见了这么多头文件为#include<unistd.h>就百度了一下:是POSIX标准定义的unix类系统定义符号常量的头文件,包含了许多UNIX系统服务的函数原型,例如read函数、write函数和getpid函数.
再谈一个puse()函数吧,
头文件:#include<unistd.h>
函数原型: int pause(void);
返回值:返回值只有-1,为数不多,至少目前我见的第一个正确返回是-1;错误给errno设置为了EINTER;
pause()函数就是前面说的挂起了,需要一个信号才行,否则一直挂起,不响应一些SIGTERM等函数,但是SIGKILL必须响应,也响应SIGUSR等,
你有没有灵光一现,sleep();那不就是封装了alarm和pause这两个函数吗哈哈,其实就是。
信号屏蔽: 信号集: 进程进程捕捉并处理的信号的集合。
用个例子介绍几个函数更清楚点。
#include<stdio.h> #include<signal.h> int main(void) { sigset_t sig_set; sigsemtyset(&sig_set); sigaddset(&sig_set, SIGHUP-1); }
先将信号集清空再设置,设置的时候信号集是从0开始的,而signo编码是下哦那个一开始,所以要减1;
前面说过默认处理方式,也有忽略,和这个屏蔽的区别是,前者是受到了信号处理时是忽略,而后者是根本都不接受,这样就产生了未决信号。
头文件:#include<signal.h>
函数原型: int sigpromask(int how,const sigset_t *restrict set, sigset_t *restrict oset);
返回值: 成功返回0 错误返回-1;
讲下how的三个值:SIG_BLOCK:set包含了处理的信号集,表示可以屏蔽了,
SIG_UNBLOCK: 从set 中杰出信号,意思是就不屏蔽了。
SIG_SETMASK:设置当前信号集未set所指向的值;
举两个简单的函数:sigpromask(SIG_BLOCK,&set,NULL) 把set设置里屏蔽信号可以屏蔽了,就是未决信号了。
sigpromask(SIG_UNBLOCK,&set,NULL) 把set设置的屏蔽信号解除了,然后未决信号就可以发送了,不必重新发送,他是自动的。
下集预告是:进程见通讯。